Skip to content

Commit 6f68a40

Browse files
authored
feat(oauth2): populate groups claim in client_credentials tokens (#4691)
Signed-off-by: Carles Arnal <carlesarnal92@gmail.com> Signed-off-by: Carles Arnal <carnalca@redhat.com>
1 parent acd853b commit 6f68a40

13 files changed

Lines changed: 247 additions & 37 deletions

File tree

server/handlers.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,12 +1919,16 @@ func (s *Server) handleClientCredentialsGrant(w http.ResponseWriter, r *http.Req
19191919
UserID: client.ID,
19201920
}
19211921

1922-
// Only populate Username/PreferredUsername when the profile scope is requested.
1922+
// Populate optional claims based on requested scopes.
19231923
for _, scope := range scopes {
1924-
if scope == scopeProfile {
1924+
switch scope {
1925+
case scopeProfile:
19251926
claims.Username = client.Name
19261927
claims.PreferredUsername = client.Name
1927-
break
1928+
case scopeGroups:
1929+
if client.ClientCredentialsClaims != nil {
1930+
claims.Groups = client.ClientCredentialsClaims.Groups
1931+
}
19281932
}
19291933
}
19301934

server/handlers_test.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,14 +1118,16 @@ func TestHandlePasswordLoginWithSkipApproval(t *testing.T) {
11181118

11191119
func TestHandleClientCredentials(t *testing.T) {
11201120
tests := []struct {
1121-
name string
1122-
clientID string
1123-
clientSecret string
1124-
scopes string
1125-
wantCode int
1126-
wantAccessTok bool
1127-
wantIDToken bool
1128-
wantUsername string
1121+
name string
1122+
clientID string
1123+
clientSecret string
1124+
clientCredentialsClaims *storage.ClientCredentialsClaims
1125+
scopes string
1126+
wantCode int
1127+
wantAccessTok bool
1128+
wantIDToken bool
1129+
wantUsername string
1130+
wantGroups []string
11291131
}{
11301132
{
11311133
name: "Basic grant, no scopes",
@@ -1165,6 +1167,28 @@ func TestHandleClientCredentials(t *testing.T) {
11651167
wantIDToken: true,
11661168
wantUsername: "Test Client",
11671169
},
1170+
{
1171+
name: "With groups scope and clientCredentialsClaims groups populated",
1172+
clientID: "test",
1173+
clientSecret: "barfoo",
1174+
clientCredentialsClaims: &storage.ClientCredentialsClaims{
1175+
Groups: []string{"admin-group", "dev-group"},
1176+
},
1177+
scopes: "openid groups",
1178+
wantCode: 200,
1179+
wantAccessTok: true,
1180+
wantIDToken: true,
1181+
wantGroups: []string{"admin-group", "dev-group"},
1182+
},
1183+
{
1184+
name: "With groups scope but no clientCredentialsClaims configured",
1185+
clientID: "test",
1186+
clientSecret: "barfoo",
1187+
scopes: "openid groups",
1188+
wantCode: 200,
1189+
wantAccessTok: true,
1190+
wantIDToken: true,
1191+
},
11681192
{
11691193
name: "Invalid client secret",
11701194
clientID: "test",
@@ -1205,10 +1229,11 @@ func TestHandleClientCredentials(t *testing.T) {
12051229

12061230
// Create a confidential client for testing.
12071231
err := s.storage.CreateClient(ctx, storage.Client{
1208-
ID: "test",
1209-
Secret: "barfoo",
1210-
RedirectURIs: []string{"https://example.com/callback"},
1211-
Name: "Test Client",
1232+
ID: "test",
1233+
Secret: "barfoo",
1234+
RedirectURIs: []string{"https://example.com/callback"},
1235+
Name: "Test Client",
1236+
ClientCredentialsClaims: tc.clientCredentialsClaims,
12121237
})
12131238
require.NoError(t, err)
12141239

@@ -1264,8 +1289,9 @@ func TestHandleClientCredentials(t *testing.T) {
12641289
require.Equal(t, tc.clientID, sub.UserId)
12651290

12661291
var claims struct {
1267-
Name string `json:"name"`
1268-
PreferredUsername string `json:"preferred_username"`
1292+
Name string `json:"name"`
1293+
PreferredUsername string `json:"preferred_username"`
1294+
Groups []string `json:"groups"`
12691295
}
12701296
require.NoError(t, idToken.Claims(&claims))
12711297

@@ -1276,6 +1302,12 @@ func TestHandleClientCredentials(t *testing.T) {
12761302
require.Empty(t, claims.Name)
12771303
require.Empty(t, claims.PreferredUsername)
12781304
}
1305+
1306+
if tc.wantGroups != nil {
1307+
require.Equal(t, tc.wantGroups, claims.Groups)
1308+
} else {
1309+
require.Empty(t, claims.Groups)
1310+
}
12791311
} else {
12801312
require.Empty(t, resp.IDToken)
12811313
}

storage/ent/client/client.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
// CreateClient saves provided oauth2 client settings into the database.
1010
func (d *Database) CreateClient(ctx context.Context, client storage.Client) error {
11-
_, err := d.client.OAuth2Client.Create().
11+
create := d.client.OAuth2Client.Create().
1212
SetID(client.ID).
1313
SetName(client.Name).
1414
SetSecret(client.Secret).
@@ -19,8 +19,11 @@ func (d *Database) CreateClient(ctx context.Context, client storage.Client) erro
1919
SetAllowedConnectors(client.AllowedConnectors).
2020
SetMfaChain(client.MFAChain).
2121
SetPostLogoutRedirectUris(client.PostLogoutRedirectURIs).
22-
SetSSOSharedWith(client.SSOSharedWith).
23-
Save(ctx)
22+
SetSSOSharedWith(client.SSOSharedWith)
23+
if client.ClientCredentialsClaims != nil {
24+
create = create.SetClientCredentialsClaims(client.ClientCredentialsClaims)
25+
}
26+
_, err := create.Save(ctx)
2427
if err != nil {
2528
return convertDBError("create oauth2 client: %w", err)
2629
}
@@ -76,7 +79,7 @@ func (d *Database) UpdateClient(ctx context.Context, id string, updater func(old
7679
return rollback(tx, "update client updating: %w", err)
7780
}
7881

79-
_, err = tx.OAuth2Client.UpdateOneID(newClient.ID).
82+
update := tx.OAuth2Client.UpdateOneID(newClient.ID).
8083
SetName(newClient.Name).
8184
SetSecret(newClient.Secret).
8285
SetPublic(newClient.Public).
@@ -86,8 +89,13 @@ func (d *Database) UpdateClient(ctx context.Context, id string, updater func(old
8689
SetAllowedConnectors(newClient.AllowedConnectors).
8790
SetMfaChain(newClient.MFAChain).
8891
SetPostLogoutRedirectUris(newClient.PostLogoutRedirectURIs).
89-
SetSSOSharedWith(newClient.SSOSharedWith).
90-
Save(ctx)
92+
SetSSOSharedWith(newClient.SSOSharedWith)
93+
if newClient.ClientCredentialsClaims != nil {
94+
update = update.SetClientCredentialsClaims(newClient.ClientCredentialsClaims)
95+
} else {
96+
update = update.ClearClientCredentialsClaims()
97+
}
98+
_, err = update.Save(ctx)
9199
if err != nil {
92100
return rollback(tx, "update client uploading: %w", err)
93101
}

storage/ent/client/types.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,18 @@ func toStorageAuthCode(a *db.AuthCode) storage.AuthCode {
8787

8888
func toStorageClient(c *db.OAuth2Client) storage.Client {
8989
return storage.Client{
90-
ID: c.ID,
91-
Secret: c.Secret,
92-
RedirectURIs: c.RedirectUris,
93-
TrustedPeers: c.TrustedPeers,
94-
Public: c.Public,
95-
Name: c.Name,
96-
LogoURL: c.LogoURL,
97-
AllowedConnectors: c.AllowedConnectors,
98-
MFAChain: c.MfaChain,
99-
PostLogoutRedirectURIs: c.PostLogoutRedirectUris,
100-
SSOSharedWith: c.SSOSharedWith,
90+
ID: c.ID,
91+
Secret: c.Secret,
92+
RedirectURIs: c.RedirectUris,
93+
TrustedPeers: c.TrustedPeers,
94+
Public: c.Public,
95+
Name: c.Name,
96+
LogoURL: c.LogoURL,
97+
AllowedConnectors: c.AllowedConnectors,
98+
MFAChain: c.MfaChain,
99+
PostLogoutRedirectURIs: c.PostLogoutRedirectUris,
100+
SSOSharedWith: c.SSOSharedWith,
101+
ClientCredentialsClaims: c.ClientCredentialsClaims,
101102
}
102103
}
103104

storage/ent/db/migrate/schema.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

storage/ent/db/mutation.go

Lines changed: 74 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

storage/ent/db/oauth2client.go

Lines changed: 16 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)