diff --git a/.schema/config.schema.json b/.schema/config.schema.json index bc365450293..34598ec1407 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -900,6 +900,11 @@ "$ref": "#/definitions/duration" } ] + }, + "omit_assertion_audience": { + "type": "boolean", + "description": "Configures whether the audience from the assertion JWT in the jwt-bearer grant type is omitted from the resulting access token. When set to `true`, the audience values from the inbound assertion JWT are not granted in the access token. Defaults to `false` for backwards compatibility.", + "default": false } } } diff --git a/driver/config/provider.go b/driver/config/provider.go index 32fdfe2227b..1fed22a17bb 100644 --- a/driver/config/provider.go +++ b/driver/config/provider.go @@ -118,6 +118,7 @@ const ( KeyOAuth2GrantJWTIDOptional = "oauth2.grant.jwt.jti_optional" KeyOAuth2GrantJWTIssuedDateOptional = "oauth2.grant.jwt.iat_optional" KeyOAuth2GrantJWTMaxDuration = "oauth2.grant.jwt.max_ttl" + KeyOAuth2GrantJWTOmitAssertionAudience = "oauth2.grant.jwt.omit_assertion_audience" KeyRefreshTokenHook = "oauth2.refresh_token_hook" // #nosec G101 KeyTokenHook = "oauth2.token_hook" // #nosec G101 KeyDevelopmentMode = "dev" @@ -740,6 +741,10 @@ func (p *DefaultProvider) GetGrantTypeJWTBearerIssuedDateOptional(ctx context.Co return p.getProvider(ctx).Bool(KeyOAuth2GrantJWTIssuedDateOptional) } +func (p *DefaultProvider) GetGrantTypeJWTBearerOmitAssertionAudience(ctx context.Context) bool { + return p.getProvider(ctx).Bool(KeyOAuth2GrantJWTOmitAssertionAudience) +} + 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 e5eadb24cfb..0cb4d465406 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, false, p.GetGrantTypeJWTBearerOmitAssertionAudience(ctx), "should default to false 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, KeyOAuth2GrantJWTOmitAssertionAudience, true) // 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, true, p2.GetGrantTypeJWTBearerOmitAssertionAudience(ctx)) } func TestJWTScopeClaimStrategy(t *testing.T) { diff --git a/fosite/config.go b/fosite/config.go index 0b7e5cd2d76..ae479a507c2 100644 --- a/fosite/config.go +++ b/fosite/config.go @@ -185,6 +185,14 @@ type GrantTypeJWTBearerIssuedDateOptionalProvider interface { GetGrantTypeJWTBearerIssuedDateOptional(ctx context.Context) bool } +// GrantTypeJWTBearerOmitAssertionAudienceProvider returns the provider for configuring whether the audience from the +// assertion JWT is omitted from the resulting access token in the jwt-bearer grant type. +type GrantTypeJWTBearerOmitAssertionAudienceProvider interface { + // GetGrantTypeJWTBearerOmitAssertionAudience returns whether the audience from the assertion JWT should be + // omitted from the resulting access token. Defaults to false for backwards compatibility. + GetGrantTypeJWTBearerOmitAssertionAudience(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..cb567a7f776 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) + _ GrantTypeJWTBearerOmitAssertionAudienceProvider = (*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 + // GrantTypeJWTBearerOmitAssertionAudience indicates whether the audience from the assertion JWT should be + // omitted from the resulting access token. Defaults to false for backwards compatibility. + GrantTypeJWTBearerOmitAssertionAudience bool + // GrantTypeJWTBearerMaxDuration sets the maximum time after JWT issued date, during which the JWT is considered valid. GrantTypeJWTBearerMaxDuration time.Duration @@ -323,6 +328,11 @@ func (c *Config) GetGrantTypeJWTBearerIDOptional(ctx context.Context) bool { return c.GrantTypeJWTBearerIDOptional } +// GetGrantTypeJWTBearerOmitAssertionAudience returns GrantTypeJWTBearerOmitAssertionAudience. +func (c *Config) GetGrantTypeJWTBearerOmitAssertionAudience(ctx context.Context) bool { + return c.GrantTypeJWTBearerOmitAssertionAudience +} + // 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..588439105b5 100644 --- a/fosite/fosite.go +++ b/fosite/fosite.go @@ -108,6 +108,7 @@ type Configurator interface { GrantTypeJWTBearerCanSkipClientAuthProvider GrantTypeJWTBearerIDOptionalProvider GrantTypeJWTBearerIssuedDateOptionalProvider + GrantTypeJWTBearerOmitAssertionAudienceProvider GetJWTMaxDurationProvider AudienceStrategyProvider ScopeStrategyProvider diff --git a/fosite/handler/rfc7523/handler.go b/fosite/handler/rfc7523/handler.go index aeed4463c72..796eb289315 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.GrantTypeJWTBearerOmitAssertionAudienceProvider 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.GetGrantTypeJWTBearerOmitAssertionAudience(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..c9114d03f11 100644 --- a/fosite/handler/rfc7523/handler_test.go +++ b/fosite/handler/rfc7523/handler_test.go @@ -728,6 +728,62 @@ 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() + s.handler.Config.(*fosite.Config).GrantTypeJWTBearerOmitAssertionAudience = true + + 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 d4e0ca7b166..bbfdb317138 100644 --- a/spec/config.json +++ b/spec/config.json @@ -900,6 +900,11 @@ "$ref": "#/definitions/duration" } ] + }, + "omit_assertion_audience": { + "type": "boolean", + "description": "Configures whether the audience from the assertion JWT in the jwt-bearer grant type is omitted from the resulting access token. When set to `true`, the audience values from the inbound assertion JWT are not granted in the access token. Defaults to `false` for backwards compatibility.", + "default": false } } }