Skip to content

Commit f237702

Browse files
committed
test(oidc): test sourcing secondary claims from access token
1 parent 69d8429 commit f237702

File tree

2 files changed

+61
-6
lines changed

2 files changed

+61
-6
lines changed

coderd/coderdtest/oidctest/idp.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ type FakeIDP struct {
105105
// "Authorized Redirect URLs". This can be used to emulate that.
106106
hookValidRedirectURL func(redirectURL string) error
107107
hookUserInfo func(email string) (jwt.MapClaims, error)
108+
hookAccessTokenJWT func(email string, exp time.Time) jwt.MapClaims
108109
// defaultIDClaims is if a new client connects and we didn't preset
109110
// some claims.
110111
defaultIDClaims jwt.MapClaims
@@ -154,6 +155,12 @@ func WithMiddlewares(mws ...func(http.Handler) http.Handler) func(*FakeIDP) {
154155
}
155156
}
156157

158+
func WithAccessTokenJWTHook(hook func(email string, exp time.Time) jwt.MapClaims) func(*FakeIDP) {
159+
return func(f *FakeIDP) {
160+
f.hookAccessTokenJWT = hook
161+
}
162+
}
163+
157164
func WithHookWellKnown(hook func(r *http.Request, j *ProviderJSON) error) func(*FakeIDP) {
158165
return func(f *FakeIDP) {
159166
f.hookWellKnown = hook
@@ -316,8 +323,7 @@ const (
316323
func NewFakeIDP(t testing.TB, opts ...FakeIDPOpt) *FakeIDP {
317324
t.Helper()
318325

319-
block, _ := pem.Decode([]byte(testRSAPrivateKey))
320-
pkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
326+
pkey, err := FakeIDPKey()
321327
require.NoError(t, err)
322328

323329
idp := &FakeIDP{
@@ -676,8 +682,13 @@ func (f *FakeIDP) newCode(state string) string {
676682

677683
// newToken enforces the access token exchanged is actually a valid access token
678684
// created by the IDP.
679-
func (f *FakeIDP) newToken(email string, expires time.Time) string {
685+
func (f *FakeIDP) newToken(t testing.TB, email string, expires time.Time) string {
680686
accessToken := uuid.NewString()
687+
if f.hookAccessTokenJWT != nil {
688+
claims := f.hookAccessTokenJWT(email, expires)
689+
accessToken = f.encodeClaims(t, claims)
690+
}
691+
681692
f.accessTokens.Store(accessToken, token{
682693
issued: time.Now(),
683694
email: email,
@@ -963,7 +974,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
963974
email := getEmail(claims)
964975
refreshToken := f.newRefreshTokens(email)
965976
token := map[string]interface{}{
966-
"access_token": f.newToken(email, exp),
977+
"access_token": f.newToken(t, email, exp),
967978
"refresh_token": refreshToken,
968979
"token_type": "Bearer",
969980
"expires_in": int64((f.defaultExpire).Seconds()),
@@ -1553,3 +1564,8 @@ d8h4Ht09E+f3nhTEc87mODkl7WJZpHL6V2sORfeq/eIkds+H6CJ4hy5w/bSw8tjf
15531564
sz9Di8sGIaUbLZI2rd0CQQCzlVwEtRtoNCyMJTTrkgUuNufLP19RZ5FpyXxBO5/u
15541565
QastnN77KfUwdj3SJt44U/uh1jAIv4oSLBr8HYUkbnI8
15551566
-----END RSA PRIVATE KEY-----`
1567+
1568+
func FakeIDPKey() (*rsa.PrivateKey, error) {
1569+
block, _ := pem.Decode([]byte(testRSAPrivateKey))
1570+
return x509.ParsePKCS1PrivateKey(block.Bytes)
1571+
}

coderd/userauth_test.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -892,13 +892,15 @@ func TestUserOIDC(t *testing.T) {
892892
Name string
893893
IDTokenClaims jwt.MapClaims
894894
UserInfoClaims jwt.MapClaims
895+
AccessTokenClaims jwt.MapClaims
895896
AllowSignups bool
896897
EmailDomain []string
897898
AssertUser func(t testing.TB, u codersdk.User)
898899
StatusCode int
899900
AssertResponse func(t testing.TB, resp *http.Response)
900901
IgnoreEmailVerified bool
901902
IgnoreUserInfo bool
903+
UseAccessToken bool
902904
}{
903905
{
904906
Name: "NoSub",
@@ -908,6 +910,32 @@ func TestUserOIDC(t *testing.T) {
908910
AllowSignups: true,
909911
StatusCode: http.StatusBadRequest,
910912
},
913+
{
914+
Name: "AccessTokenMerge",
915+
IDTokenClaims: jwt.MapClaims{
916+
"sub": uuid.NewString(),
917+
},
918+
AccessTokenClaims: jwt.MapClaims{
919+
"email": "kyle@kwc.io",
920+
},
921+
IgnoreUserInfo: true,
922+
AllowSignups: true,
923+
UseAccessToken: true,
924+
StatusCode: http.StatusOK,
925+
AssertUser: func(t testing.TB, u codersdk.User) {
926+
assert.Equal(t, "kyle@kwc.io", u.Email)
927+
},
928+
},
929+
{
930+
Name: "AccessTokenMergeNotJWT",
931+
IDTokenClaims: jwt.MapClaims{
932+
"sub": uuid.NewString(),
933+
},
934+
IgnoreUserInfo: true,
935+
AllowSignups: true,
936+
UseAccessToken: true,
937+
StatusCode: http.StatusBadRequest,
938+
},
911939
{
912940
Name: "EmailOnly",
913941
IDTokenClaims: jwt.MapClaims{
@@ -1290,20 +1318,31 @@ func TestUserOIDC(t *testing.T) {
12901318
tc := tc
12911319
t.Run(tc.Name, func(t *testing.T) {
12921320
t.Parallel()
1293-
fake := oidctest.NewFakeIDP(t,
1321+
opts := []oidctest.FakeIDPOpt{
12941322
oidctest.WithRefresh(func(_ string) error {
12951323
return xerrors.New("refreshing token should never occur")
12961324
}),
12971325
oidctest.WithServing(),
12981326
oidctest.WithStaticUserInfo(tc.UserInfoClaims),
1299-
)
1327+
}
1328+
1329+
if tc.AccessTokenClaims != nil && len(tc.AccessTokenClaims) > 0 {
1330+
opts = append(opts, oidctest.WithAccessTokenJWTHook(func(email string, exp time.Time) jwt.MapClaims {
1331+
return tc.AccessTokenClaims
1332+
}))
1333+
}
1334+
1335+
fake := oidctest.NewFakeIDP(t, opts...)
13001336
cfg := fake.OIDCConfig(t, nil, func(cfg *coderd.OIDCConfig) {
13011337
cfg.AllowSignups = tc.AllowSignups
13021338
cfg.EmailDomain = tc.EmailDomain
13031339
cfg.IgnoreEmailVerified = tc.IgnoreEmailVerified
13041340
if tc.IgnoreUserInfo {
13051341
cfg.SecondaryClaims = coderd.MergedClaimsSourceUserInfo
13061342
}
1343+
if tc.UseAccessToken {
1344+
cfg.SecondaryClaims = coderd.MergedClaimsSourceAccessToken
1345+
}
13071346
cfg.NameField = "name"
13081347
})
13091348

0 commit comments

Comments
 (0)