diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 8b1d6800c81..7740e55c0a2 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -894,6 +894,11 @@ "$ref": "#/definitions/duration" } ] + }, + "copy_assertion_audience": { + "type": "boolean", + "description": "Configures whether the audience from the assertion JWT in the jwt-bearer grant type is copied into the resulting access token. When set to `true` (the default), the audience values from the inbound assertion JWT are granted in the access token. Set to `false` to prevent the assertion audience from being copied into the access token.", + "default": true } } } diff --git a/driver/config/provider.go b/driver/config/provider.go index 6670b4a05bc..d29c7dfa0b8 100644 --- a/driver/config/provider.go +++ b/driver/config/provider.go @@ -117,6 +117,7 @@ const ( KeyOAuth2GrantJWTIDOptional = "oauth2.grant.jwt.jti_optional" KeyOAuth2GrantJWTIssuedDateOptional = "oauth2.grant.jwt.iat_optional" KeyOAuth2GrantJWTMaxDuration = "oauth2.grant.jwt.max_ttl" + KeyOAuth2GrantJWTCopyAssertionAudience = "oauth2.grant.jwt.copy_assertion_audience" KeyRefreshTokenHook = "oauth2.refresh_token_hook" // #nosec G101 KeyTokenHook = "oauth2.token_hook" // #nosec G101 KeyDevelopmentMode = "dev" @@ -724,6 +725,10 @@ func (p *DefaultProvider) GetGrantTypeJWTBearerIssuedDateOptional(ctx context.Co return p.getProvider(ctx).Bool(KeyOAuth2GrantJWTIssuedDateOptional) } +func (p *DefaultProvider) GetGrantTypeJWTBearerCopyAssertionAudience(ctx context.Context) bool { + return p.getProvider(ctx).BoolF(KeyOAuth2GrantJWTCopyAssertionAudience, true) +} + func (p *DefaultProvider) GetJWTMaxDuration(ctx context.Context) time.Duration { return p.getProvider(ctx).DurationF(KeyOAuth2GrantJWTMaxDuration, time.Hour*24*30) } diff --git a/driver/config/provider_test.go b/driver/config/provider_test.go index 7ffb80a0b42..4eb45219979 100644 --- a/driver/config/provider_test.go +++ b/driver/config/provider_test.go @@ -502,6 +502,7 @@ func TestJWTBearer(t *testing.T) { assert.Equal(t, 1.0, p.GetJWTMaxDuration(ctx).Hours()) assert.Equal(t, false, p.GetGrantTypeJWTBearerIssuedDateOptional(ctx)) assert.Equal(t, false, p.GetGrantTypeJWTBearerIDOptional(ctx)) + assert.Equal(t, true, p.GetGrantTypeJWTBearerCopyAssertionAudience(ctx), "should default to true when not set") p2 := MustNew(t, l) @@ -509,11 +510,13 @@ func TestJWTBearer(t *testing.T) { p2.MustSet(ctx, KeyOAuth2GrantJWTMaxDuration, "24h") p2.MustSet(ctx, KeyOAuth2GrantJWTIssuedDateOptional, true) p2.MustSet(ctx, KeyOAuth2GrantJWTIDOptional, true) + p2.MustSet(ctx, KeyOAuth2GrantJWTCopyAssertionAudience, false) // assert.Equal(t, true, p2.GetGrantTypeJWTBearerCanSkipClientAuth(ctx)) assert.Equal(t, 24.0, p2.GetJWTMaxDuration(ctx).Hours()) assert.Equal(t, true, p2.GetGrantTypeJWTBearerIssuedDateOptional(ctx)) assert.Equal(t, true, p2.GetGrantTypeJWTBearerIDOptional(ctx)) + assert.Equal(t, false, p2.GetGrantTypeJWTBearerCopyAssertionAudience(ctx)) } func TestJWTScopeClaimStrategy(t *testing.T) { diff --git a/fosite/config.go b/fosite/config.go index 0b7e5cd2d76..87eb442c977 100644 --- a/fosite/config.go +++ b/fosite/config.go @@ -185,6 +185,14 @@ type GrantTypeJWTBearerIssuedDateOptionalProvider interface { GetGrantTypeJWTBearerIssuedDateOptional(ctx context.Context) bool } +// GrantTypeJWTBearerCopyAssertionAudienceProvider returns the provider for configuring whether the audience from the +// assertion JWT is copied into the resulting access token in the jwt-bearer grant type. +type GrantTypeJWTBearerCopyAssertionAudienceProvider interface { + // GetGrantTypeJWTBearerCopyAssertionAudience returns whether the audience from the assertion JWT should be + // copied into the resulting access token. Defaults to true for backwards compatibility. + GetGrantTypeJWTBearerCopyAssertionAudience(ctx context.Context) bool +} + // GetJWTMaxDurationProvider returns the provider for configuring the JWT max duration. type GetJWTMaxDurationProvider interface { // GetJWTMaxDuration returns the JWT max duration. diff --git a/fosite/config_default.go b/fosite/config_default.go index 52011112792..f3a2a3e5651 100644 --- a/fosite/config_default.go +++ b/fosite/config_default.go @@ -26,45 +26,46 @@ const ( ) var ( - _ AuthorizeCodeLifespanProvider = (*Config)(nil) - _ RefreshTokenLifespanProvider = (*Config)(nil) - _ AccessTokenLifespanProvider = (*Config)(nil) - _ ScopeStrategyProvider = (*Config)(nil) - _ AudienceStrategyProvider = (*Config)(nil) - _ RedirectSecureCheckerProvider = (*Config)(nil) - _ RefreshTokenScopesProvider = (*Config)(nil) - _ DisableRefreshTokenValidationProvider = (*Config)(nil) - _ AccessTokenIssuerProvider = (*Config)(nil) - _ JWTScopeFieldProvider = (*Config)(nil) - _ AllowedPromptsProvider = (*Config)(nil) - _ OmitRedirectScopeParamProvider = (*Config)(nil) - _ MinParameterEntropyProvider = (*Config)(nil) - _ SanitationAllowedProvider = (*Config)(nil) - _ EnforcePKCEForPublicClientsProvider = (*Config)(nil) - _ EnablePKCEPlainChallengeMethodProvider = (*Config)(nil) - _ EnforcePKCEProvider = (*Config)(nil) - _ GrantTypeJWTBearerCanSkipClientAuthProvider = (*Config)(nil) - _ GrantTypeJWTBearerIDOptionalProvider = (*Config)(nil) - _ GrantTypeJWTBearerIssuedDateOptionalProvider = (*Config)(nil) - _ GetJWTMaxDurationProvider = (*Config)(nil) - _ IDTokenLifespanProvider = (*Config)(nil) - _ IDTokenIssuerProvider = (*Config)(nil) - _ JWKSFetcherStrategyProvider = (*Config)(nil) - _ ClientAuthenticationStrategyProvider = (*Config)(nil) - _ SendDebugMessagesToClientsProvider = (*Config)(nil) - _ ResponseModeHandlerExtensionProvider = (*Config)(nil) - _ MessageCatalogProvider = (*Config)(nil) - _ FormPostHTMLTemplateProvider = (*Config)(nil) - _ TokenURLProvider = (*Config)(nil) - _ GetSecretsHashingProvider = (*Config)(nil) - _ HTTPClientProvider = (*Config)(nil) - _ HMACHashingProvider = (*Config)(nil) - _ AuthorizeEndpointHandlersProvider = (*Config)(nil) - _ TokenEndpointHandlersProvider = (*Config)(nil) - _ TokenIntrospectionHandlersProvider = (*Config)(nil) - _ RevocationHandlersProvider = (*Config)(nil) - _ PushedAuthorizeRequestHandlersProvider = (*Config)(nil) - _ PushedAuthorizeRequestConfigProvider = (*Config)(nil) + _ AuthorizeCodeLifespanProvider = (*Config)(nil) + _ RefreshTokenLifespanProvider = (*Config)(nil) + _ AccessTokenLifespanProvider = (*Config)(nil) + _ ScopeStrategyProvider = (*Config)(nil) + _ AudienceStrategyProvider = (*Config)(nil) + _ RedirectSecureCheckerProvider = (*Config)(nil) + _ RefreshTokenScopesProvider = (*Config)(nil) + _ DisableRefreshTokenValidationProvider = (*Config)(nil) + _ AccessTokenIssuerProvider = (*Config)(nil) + _ JWTScopeFieldProvider = (*Config)(nil) + _ AllowedPromptsProvider = (*Config)(nil) + _ OmitRedirectScopeParamProvider = (*Config)(nil) + _ MinParameterEntropyProvider = (*Config)(nil) + _ SanitationAllowedProvider = (*Config)(nil) + _ EnforcePKCEForPublicClientsProvider = (*Config)(nil) + _ EnablePKCEPlainChallengeMethodProvider = (*Config)(nil) + _ EnforcePKCEProvider = (*Config)(nil) + _ GrantTypeJWTBearerCanSkipClientAuthProvider = (*Config)(nil) + _ GrantTypeJWTBearerIDOptionalProvider = (*Config)(nil) + _ GrantTypeJWTBearerIssuedDateOptionalProvider = (*Config)(nil) + _ GrantTypeJWTBearerCopyAssertionAudienceProvider = (*Config)(nil) + _ GetJWTMaxDurationProvider = (*Config)(nil) + _ IDTokenLifespanProvider = (*Config)(nil) + _ IDTokenIssuerProvider = (*Config)(nil) + _ JWKSFetcherStrategyProvider = (*Config)(nil) + _ ClientAuthenticationStrategyProvider = (*Config)(nil) + _ SendDebugMessagesToClientsProvider = (*Config)(nil) + _ ResponseModeHandlerExtensionProvider = (*Config)(nil) + _ MessageCatalogProvider = (*Config)(nil) + _ FormPostHTMLTemplateProvider = (*Config)(nil) + _ TokenURLProvider = (*Config)(nil) + _ GetSecretsHashingProvider = (*Config)(nil) + _ HTTPClientProvider = (*Config)(nil) + _ HMACHashingProvider = (*Config)(nil) + _ AuthorizeEndpointHandlersProvider = (*Config)(nil) + _ TokenEndpointHandlersProvider = (*Config)(nil) + _ TokenIntrospectionHandlersProvider = (*Config)(nil) + _ RevocationHandlersProvider = (*Config)(nil) + _ PushedAuthorizeRequestHandlersProvider = (*Config)(nil) + _ PushedAuthorizeRequestConfigProvider = (*Config)(nil) ) type Config struct { @@ -160,6 +161,10 @@ type Config struct { // GrantTypeJWTBearerIssuedDateOptional indicates, if "iat" (issued at) claim required or not in JWT. GrantTypeJWTBearerIssuedDateOptional bool + // GrantTypeJWTBearerCopyAssertionAudience indicates whether the audience from the assertion JWT should be + // copied into the resulting access token. Defaults to true for backwards compatibility. + GrantTypeJWTBearerCopyAssertionAudience *bool + // GrantTypeJWTBearerMaxDuration sets the maximum time after JWT issued date, during which the JWT is considered valid. GrantTypeJWTBearerMaxDuration time.Duration @@ -323,6 +328,15 @@ func (c *Config) GetGrantTypeJWTBearerIDOptional(ctx context.Context) bool { return c.GrantTypeJWTBearerIDOptional } +// GetGrantTypeJWTBearerCopyAssertionAudience returns GrantTypeJWTBearerCopyAssertionAudience. +// Defaults to true if not explicitly set, for backwards compatibility. +func (c *Config) GetGrantTypeJWTBearerCopyAssertionAudience(ctx context.Context) bool { + if c.GrantTypeJWTBearerCopyAssertionAudience == nil { + return true + } + return *c.GrantTypeJWTBearerCopyAssertionAudience +} + // GetGrantTypeJWTBearerCanSkipClientAuth returns the GrantTypeJWTBearerCanSkipClientAuth field. func (c *Config) GetGrantTypeJWTBearerCanSkipClientAuth(ctx context.Context) bool { return c.GrantTypeJWTBearerCanSkipClientAuth diff --git a/fosite/fosite.go b/fosite/fosite.go index b7ab0547c6c..9ed7398cc60 100644 --- a/fosite/fosite.go +++ b/fosite/fosite.go @@ -108,6 +108,7 @@ type Configurator interface { GrantTypeJWTBearerCanSkipClientAuthProvider GrantTypeJWTBearerIDOptionalProvider GrantTypeJWTBearerIssuedDateOptionalProvider + GrantTypeJWTBearerCopyAssertionAudienceProvider GetJWTMaxDurationProvider AudienceStrategyProvider ScopeStrategyProvider diff --git a/fosite/handler/rfc7523/handler.go b/fosite/handler/rfc7523/handler.go index aeed4463c72..b73606c2a1c 100644 --- a/fosite/handler/rfc7523/handler.go +++ b/fosite/handler/rfc7523/handler.go @@ -34,6 +34,7 @@ type Handler struct { fosite.GrantTypeJWTBearerCanSkipClientAuthProvider fosite.GrantTypeJWTBearerIDOptionalProvider fosite.GrantTypeJWTBearerIssuedDateOptionalProvider + fosite.GrantTypeJWTBearerCopyAssertionAudienceProvider fosite.GetJWTMaxDurationProvider fosite.AudienceStrategyProvider fosite.ScopeStrategyProvider @@ -103,8 +104,10 @@ func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite request.GrantScope(scope) } - for _, audience := range claims.Audience { - request.GrantAudience(audience) + if c.Config.GetGrantTypeJWTBearerCopyAssertionAudience(ctx) { + for _, audience := range claims.Audience { + request.GrantAudience(audience) + } } session, err := c.getSessionFromRequest(request) diff --git a/fosite/handler/rfc7523/handler_test.go b/fosite/handler/rfc7523/handler_test.go index 2f7ace9dd12..f8eca148185 100644 --- a/fosite/handler/rfc7523/handler_test.go +++ b/fosite/handler/rfc7523/handler_test.go @@ -728,6 +728,63 @@ func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestValidAssertion() { s.NoError(err, "no error expected, because assertion must be valid") } +func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestValidAssertionCopiesAudienceByDefault() { + // arrange + ctx := context.Background() + s.accessRequest.GrantTypes = []string{grantTypeJWTBearer} + keyID := "my_key" + pubKey := s.createJWK(s.privateKey.Public(), keyID) + cl := s.createStandardClaim() + + s.accessRequest.Form.Add("assertion", s.createTestAssertion(cl, keyID)) + s.accessRequest.RequestedScope = []string{"valid_scope"} + s.mockStoreProvider.EXPECT().RFC7523KeyStorage().Return(s.mockStore).Times(4) + s.mockStore.EXPECT().GetPublicKey(ctx, cl.Issuer, cl.Subject, keyID).Return(&pubKey, nil) + s.mockStore.EXPECT().GetPublicKeyScopes(ctx, cl.Issuer, cl.Subject, keyID).Return([]string{"valid_scope", "openid"}, nil) + s.mockStore.EXPECT().IsJWTUsed(ctx, cl.ID).Return(false, nil) + s.mockStore.EXPECT().MarkJWTUsedForTime(ctx, cl.ID, cl.Expiry.Time()).Return(nil) + + // act + err := s.handler.HandleTokenEndpointRequest(ctx, s.accessRequest) + + // assert + s.NoError(err, "no error expected, because assertion must be valid") + s.ElementsMatch( + []string{"https://www.example.com/token", "leela", "fry"}, + s.accessRequest.GetGrantedAudience(), + "audience from assertion JWT should be copied to the access request by default", + ) +} + +func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestValidAssertionDoesNotCopyAudienceWhenDisabled() { + // arrange + ctx := context.Background() + s.accessRequest.GrantTypes = []string{grantTypeJWTBearer} + keyID := "my_key" + pubKey := s.createJWK(s.privateKey.Public(), keyID) + cl := s.createStandardClaim() + copyAudience := false + s.handler.Config.(*fosite.Config).GrantTypeJWTBearerCopyAssertionAudience = ©Audience + + s.accessRequest.Form.Add("assertion", s.createTestAssertion(cl, keyID)) + s.accessRequest.RequestedScope = []string{"valid_scope"} + s.mockStoreProvider.EXPECT().RFC7523KeyStorage().Return(s.mockStore).Times(4) + s.mockStore.EXPECT().GetPublicKey(ctx, cl.Issuer, cl.Subject, keyID).Return(&pubKey, nil) + s.mockStore.EXPECT().GetPublicKeyScopes(ctx, cl.Issuer, cl.Subject, keyID).Return([]string{"valid_scope", "openid"}, nil) + s.mockStore.EXPECT().IsJWTUsed(ctx, cl.ID).Return(false, nil) + s.mockStore.EXPECT().MarkJWTUsedForTime(ctx, cl.ID, cl.Expiry.Time()).Return(nil) + + // act + err := s.handler.HandleTokenEndpointRequest(ctx, s.accessRequest) + + // assert + s.NoError(err, "no error expected, because assertion must be valid") + s.Empty( + s.accessRequest.GetGrantedAudience(), + "audience from assertion JWT should NOT be copied when CopyAssertionAudience is false", + ) +} + func (s *AuthorizeJWTGrantRequestHandlerTestSuite) TestAssertionIsValidWhenNoScopesPassed() { // arrange ctx := context.Background() diff --git a/spec/config.json b/spec/config.json index c298f099563..92a2b6d4b76 100644 --- a/spec/config.json +++ b/spec/config.json @@ -894,6 +894,11 @@ "$ref": "#/definitions/duration" } ] + }, + "copy_assertion_audience": { + "type": "boolean", + "description": "Configures whether the audience from the assertion JWT in the jwt-bearer grant type is copied into the resulting access token. When set to `true` (the default), the audience values from the inbound assertion JWT are granted in the access token. Set to `false` to prevent the assertion audience from being copied into the access token.", + "default": true } } }