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

View File

@ -105,6 +105,7 @@ type FakeIDP struct {
// "Authorized Redirect URLs". This can be used to emulate that.
hookValidRedirectURL func(redirectURL string) error
hookUserInfo func(email string) (jwt.MapClaims, error)
hookAccessTokenJWT func(email string, exp time.Time) jwt.MapClaims
// defaultIDClaims is if a new client connects and we didn't preset
// some claims.
defaultIDClaims jwt.MapClaims
@ -154,6 +155,12 @@ func WithMiddlewares(mws ...func(http.Handler) http.Handler) func(*FakeIDP) {
}
}
func WithAccessTokenJWTHook(hook func(email string, exp time.Time) jwt.MapClaims) func(*FakeIDP) {
return func(f *FakeIDP) {
f.hookAccessTokenJWT = hook
}
}
func WithHookWellKnown(hook func(r *http.Request, j *ProviderJSON) error) func(*FakeIDP) {
return func(f *FakeIDP) {
f.hookWellKnown = hook
@ -316,8 +323,7 @@ const (
func NewFakeIDP(t testing.TB, opts ...FakeIDPOpt) *FakeIDP {
t.Helper()
block, _ := pem.Decode([]byte(testRSAPrivateKey))
pkey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
pkey, err := FakeIDPKey()
require.NoError(t, err)
idp := &FakeIDP{
@ -676,8 +682,13 @@ func (f *FakeIDP) newCode(state string) string {
// newToken enforces the access token exchanged is actually a valid access token
// created by the IDP.
func (f *FakeIDP) newToken(email string, expires time.Time) string {
func (f *FakeIDP) newToken(t testing.TB, email string, expires time.Time) string {
accessToken := uuid.NewString()
if f.hookAccessTokenJWT != nil {
claims := f.hookAccessTokenJWT(email, expires)
accessToken = f.encodeClaims(t, claims)
}
f.accessTokens.Store(accessToken, token{
issued: time.Now(),
email: email,
@ -963,7 +974,7 @@ func (f *FakeIDP) httpHandler(t testing.TB) http.Handler {
email := getEmail(claims)
refreshToken := f.newRefreshTokens(email)
token := map[string]interface{}{
"access_token": f.newToken(email, exp),
"access_token": f.newToken(t, email, exp),
"refresh_token": refreshToken,
"token_type": "Bearer",
"expires_in": int64((f.defaultExpire).Seconds()),
@ -1465,9 +1476,10 @@ func (f *FakeIDP) internalOIDCConfig(ctx context.Context, t testing.TB, scopes [
Verifier: oidc.NewVerifier(f.provider.Issuer, &oidc.StaticKeySet{
PublicKeys: []crypto.PublicKey{f.key.Public()},
}, verifierConfig),
UsernameField: "preferred_username",
EmailField: "email",
AuthURLParams: map[string]string{"access_type": "offline"},
UsernameField: "preferred_username",
EmailField: "email",
AuthURLParams: map[string]string{"access_type": "offline"},
SecondaryClaims: coderd.MergedClaimsSourceUserInfo,
}
for _, opt := range opts {
@ -1552,3 +1564,8 @@ d8h4Ht09E+f3nhTEc87mODkl7WJZpHL6V2sORfeq/eIkds+H6CJ4hy5w/bSw8tjf
sz9Di8sGIaUbLZI2rd0CQQCzlVwEtRtoNCyMJTTrkgUuNufLP19RZ5FpyXxBO5/u
QastnN77KfUwdj3SJt44U/uh1jAIv4oSLBr8HYUkbnI8
-----END RSA PRIVATE KEY-----`
func FakeIDPKey() (*rsa.PrivateKey, error) {
block, _ := pem.Decode([]byte(testRSAPrivateKey))
return x509.ParsePKCS1PrivateKey(block.Bytes)
}