feat: add sourcing secondary claims from access_token (#16517)

Niche edge case, assumes access_token is jwt. 

Some `access_token`s are JWT's with potential useful claims.
These claims would be nearly equivalent to `user_info` claims.
This is not apart of the oauth spec, so this feature should not be
loudly advertised. If using this feature, alternate solutions are preferred.
This commit is contained in:
Steven Masley
2025-02-24 13:38:20 -06:00
committed by GitHub
parent e005e4e51d
commit 658825cad2
12 changed files with 282 additions and 100 deletions
+46 -4
View File
@@ -61,7 +61,7 @@ func TestOIDCOauthLoginWithExisting(t *testing.T) {
cfg := fake.OIDCConfig(t, nil, func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = true
cfg.IgnoreUserInfo = true
cfg.SecondaryClaims = coderd.MergedClaimsSourceNone
})
client, _, api := coderdtest.NewWithAPI(t, &coderdtest.Options{
@@ -979,6 +979,7 @@ func TestUserOIDC(t *testing.T) {
Name string
IDTokenClaims jwt.MapClaims
UserInfoClaims jwt.MapClaims
AccessTokenClaims jwt.MapClaims
AllowSignups bool
EmailDomain []string
AssertUser func(t testing.TB, u codersdk.User)
@@ -986,6 +987,7 @@ func TestUserOIDC(t *testing.T) {
AssertResponse func(t testing.TB, resp *http.Response)
IgnoreEmailVerified bool
IgnoreUserInfo bool
UseAccessToken bool
}{
{
Name: "NoSub",
@@ -995,6 +997,32 @@ func TestUserOIDC(t *testing.T) {
AllowSignups: true,
StatusCode: http.StatusBadRequest,
},
{
Name: "AccessTokenMerge",
IDTokenClaims: jwt.MapClaims{
"sub": uuid.NewString(),
},
AccessTokenClaims: jwt.MapClaims{
"email": "kyle@kwc.io",
},
IgnoreUserInfo: true,
AllowSignups: true,
UseAccessToken: true,
StatusCode: http.StatusOK,
AssertUser: func(t testing.TB, u codersdk.User) {
assert.Equal(t, "kyle@kwc.io", u.Email)
},
},
{
Name: "AccessTokenMergeNotJWT",
IDTokenClaims: jwt.MapClaims{
"sub": uuid.NewString(),
},
IgnoreUserInfo: true,
AllowSignups: true,
UseAccessToken: true,
StatusCode: http.StatusBadRequest,
},
{
Name: "EmailOnly",
IDTokenClaims: jwt.MapClaims{
@@ -1377,18 +1405,32 @@ func TestUserOIDC(t *testing.T) {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
fake := oidctest.NewFakeIDP(t,
opts := []oidctest.FakeIDPOpt{
oidctest.WithRefresh(func(_ string) error {
return xerrors.New("refreshing token should never occur")
}),
oidctest.WithServing(),
oidctest.WithStaticUserInfo(tc.UserInfoClaims),
)
}
if tc.AccessTokenClaims != nil && len(tc.AccessTokenClaims) > 0 {
opts = append(opts, oidctest.WithAccessTokenJWTHook(func(email string, exp time.Time) jwt.MapClaims {
return tc.AccessTokenClaims
}))
}
fake := oidctest.NewFakeIDP(t, opts...)
cfg := fake.OIDCConfig(t, nil, func(cfg *coderd.OIDCConfig) {
cfg.AllowSignups = tc.AllowSignups
cfg.EmailDomain = tc.EmailDomain
cfg.IgnoreEmailVerified = tc.IgnoreEmailVerified
cfg.IgnoreUserInfo = tc.IgnoreUserInfo
cfg.SecondaryClaims = coderd.MergedClaimsSourceUserInfo
if tc.IgnoreUserInfo {
cfg.SecondaryClaims = coderd.MergedClaimsSourceNone
}
if tc.UseAccessToken {
cfg.SecondaryClaims = coderd.MergedClaimsSourceAccessToken
}
cfg.NameField = "name"
})