Browse Source

fix(oauth2): scope-conditional claims and reserved connector ID for client_credentials

Signed-off-by: Mathias Gebbe <mathias.gebbe@gmail.com>
pull/4583/head
Mathias Gebbe 3 weeks ago
parent
commit
b652d5b2a0
No known key found for this signature in database
GPG Key ID: 2A35E2EC75E5438F
  1. 15
      server/handlers.go
  2. 54
      server/handlers_test.go

15
server/handlers.go

@ -1526,12 +1526,19 @@ func (s *Server) handleClientCredentialsGrant(w http.ResponseWriter, r *http.Req
// Build claims from the client itself — no user involved.
claims := storage.Claims{
UserID: client.ID,
Username: client.Name,
PreferredUsername: client.Name,
UserID: client.ID,
}
connID := "client_credentials"
// Only populate Username/PreferredUsername when the profile scope is requested.
for _, scope := range scopes {
if scope == scopeProfile {
claims.Username = client.Name
claims.PreferredUsername = client.Name
break
}
}
connID := "__client_credentials"
accessToken, expiry, err := s.newAccessToken(ctx, client.ID, claims, scopes, "", connID)
if err != nil {

54
server/handlers_test.go

@ -21,6 +21,7 @@ import (
"golang.org/x/crypto/bcrypt"
"golang.org/x/oauth2"
"github.com/dexidp/dex/server/internal"
"github.com/dexidp/dex/storage"
)
@ -648,13 +649,14 @@ func TestHandlePasswordLoginWithSkipApproval(t *testing.T) {
func TestHandleClientCredentials(t *testing.T) {
tests := []struct {
name string
clientID string
clientSecret string
scopes string
wantCode int
wantAccessTok bool
wantIDToken bool
name string
clientID string
clientSecret string
scopes string
wantCode int
wantAccessTok bool
wantIDToken bool
wantUsername string
}{
{
name: "Basic grant, no scopes",
@ -674,6 +676,16 @@ func TestHandleClientCredentials(t *testing.T) {
wantAccessTok: true,
wantIDToken: true,
},
{
name: "With openid and profile scope includes username",
clientID: "test",
clientSecret: "barfoo",
scopes: "openid profile",
wantCode: 200,
wantAccessTok: true,
wantIDToken: true,
wantUsername: "Test Client",
},
{
name: "With openid email profile groups",
clientID: "test",
@ -682,6 +694,7 @@ func TestHandleClientCredentials(t *testing.T) {
wantCode: 200,
wantAccessTok: true,
wantIDToken: true,
wantUsername: "Test Client",
},
{
name: "Invalid client secret",
@ -767,6 +780,33 @@ func TestHandleClientCredentials(t *testing.T) {
}
if tc.wantIDToken {
require.NotEmpty(t, resp.IDToken)
// Verify the ID token claims.
provider, err := oidc.NewProvider(ctx, httpServer.URL)
require.NoError(t, err)
verifier := provider.Verifier(&oidc.Config{ClientID: tc.clientID})
idToken, err := verifier.Verify(ctx, resp.IDToken)
require.NoError(t, err)
// Decode the subject to verify the connector ID.
var sub internal.IDTokenSubject
require.NoError(t, internal.Unmarshal(idToken.Subject, &sub))
require.Equal(t, "__client_credentials", sub.ConnId)
require.Equal(t, tc.clientID, sub.UserId)
var claims struct {
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
}
require.NoError(t, idToken.Claims(&claims))
if tc.wantUsername != "" {
require.Equal(t, tc.wantUsername, claims.Name)
require.Equal(t, tc.wantUsername, claims.PreferredUsername)
} else {
require.Empty(t, claims.Name)
require.Empty(t, claims.PreferredUsername)
}
} else {
require.Empty(t, resp.IDToken)
}

Loading…
Cancel
Save