From 45da11e4fb4f0a2f939f11682c095b8dbfcddb78 Mon Sep 17 00:00:00 2001 From: Brett Patterson Date: Wed, 5 Jul 2023 05:26:08 -0400 Subject: [PATCH] feat: support different jwt scope claim strategies (#3531) --- .schema/config.schema.json | 12 ++++++++++++ Makefile | 2 +- cmd/cmd_list_clients.go | 1 + driver/config/provider.go | 1 + driver/config/provider_fosite.go | 16 ++++++++++++++++ driver/config/provider_test.go | 18 ++++++++++++++++++ fositex/config.go | 2 +- spec/config.json | 12 ++++++++++++ 8 files changed, 62 insertions(+), 2 deletions(-) diff --git a/.schema/config.schema.json b/.schema/config.schema.json index d792093700b..b1b389594ac 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -781,6 +781,18 @@ "description": "Defines access token type. jwt is a bad idea, see https://www.ory.sh/docs/hydra/advanced#json-web-tokens", "enum": ["opaque", "jwt"], "default": "opaque" + }, + "jwt": { + "type": "object", + "additionalProperties": false, + "properties": { + "scope_claim": { + "type": "string", + "description": "Defines how the scope claim is represented within a JWT access token", + "enum": ["list", "string", "both"], + "default": "list" + } + } } } }, diff --git a/Makefile b/Makefile index 8adf128efd3..e8b512af9bf 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ export PATH := .bin:${PATH} export PWD := $(shell pwd) export IMAGE_TAG := $(if $(IMAGE_TAG),$(IMAGE_TAG),latest) -GOLANGCI_LINT_VERSION = 1.53.2 +GOLANGCI_LINT_VERSION = 1.53.3 GO_DEPENDENCIES = github.com/ory/go-acc \ github.com/golang/mock/mockgen \ diff --git a/cmd/cmd_list_clients.go b/cmd/cmd_list_clients.go index ddaf2762018..41fab8c513f 100644 --- a/cmd/cmd_list_clients.go +++ b/cmd/cmd_list_clients.go @@ -31,6 +31,7 @@ func NewListClientsCmd() *cobra.Command { return err } + // nolint:bodyclose list, resp, err := m.OAuth2Api.ListOAuth2Clients(cmd.Context()).PageSize(int64(pageSize)).PageToken(pageToken).Execute() if err != nil { return cmdx.PrintOpenAPIError(cmd, err) diff --git a/driver/config/provider.go b/driver/config/provider.go index 74d4179d7cf..a6149d7e5a6 100644 --- a/driver/config/provider.go +++ b/driver/config/provider.go @@ -79,6 +79,7 @@ const ( KeyAdminURL = "urls.self.admin" KeyIssuerURL = "urls.self.issuer" KeyAccessTokenStrategy = "strategies.access_token" + KeyJWTScopeClaimStrategy = "strategies.jwt.scope_claim" KeyDBIgnoreUnknownTableColumns = "db.ignore_unknown_table_columns" KeySubjectIdentifierAlgorithmSalt = "oidc.subject_identifiers.pairwise.salt" KeyPublicAllowDynamicRegistration = "oidc.dynamic_client_registration.enabled" diff --git a/driver/config/provider_fosite.go b/driver/config/provider_fosite.go index 3f8b7758b11..053f0af10ab 100644 --- a/driver/config/provider_fosite.go +++ b/driver/config/provider_fosite.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" "github.com/ory/fosite" + "github.com/ory/fosite/token/jwt" "github.com/ory/hydra/v2/x" ) @@ -89,6 +90,21 @@ func (p *DefaultProvider) GetScopeStrategy(ctx context.Context) fosite.ScopeStra return fosite.ExactScopeStrategy } +var _ fosite.JWTScopeFieldProvider = (*DefaultProvider)(nil) + +func (p *DefaultProvider) GetJWTScopeField(ctx context.Context) jwt.JWTScopeFieldEnum { + switch strings.ToLower(p.getProvider(ctx).String(KeyJWTScopeClaimStrategy)) { + case "string": + return jwt.JWTScopeFieldString + case "both": + return jwt.JWTScopeFieldBoth + case "list": + return jwt.JWTScopeFieldList + default: + return jwt.JWTScopeFieldUnset + } +} + func (p *DefaultProvider) GetUseLegacyErrorFormat(context.Context) bool { return false } diff --git a/driver/config/provider_test.go b/driver/config/provider_test.go index f7f3b4aef89..b4b42c33dd3 100644 --- a/driver/config/provider_test.go +++ b/driver/config/provider_test.go @@ -14,6 +14,7 @@ import ( "testing" "time" + "github.com/ory/fosite/token/jwt" "github.com/ory/x/configx" "github.com/ory/x/otelx" @@ -305,6 +306,7 @@ func TestViperProviderValidates(t *testing.T) { assert.False(t, c.GetScopeStrategy(ctx)([]string{"openid.*"}, "openid.email"), "should us fosite.ExactScopeStrategy") assert.Equal(t, AccessTokenDefaultStrategy, c.AccessTokenStrategy(ctx)) assert.Equal(t, false, c.GrantAllClientCredentialsScopesPerDefault(ctx)) + assert.Equal(t, jwt.JWTScopeFieldList, c.GetJWTScopeField(ctx)) // ttl assert.Equal(t, 2*time.Hour, c.ConsentRequestMaxAge(ctx)) @@ -464,3 +466,19 @@ func TestJWTBearer(t *testing.T) { assert.Equal(t, true, p2.GetGrantTypeJWTBearerIssuedDateOptional(ctx)) assert.Equal(t, true, p2.GetGrantTypeJWTBearerIDOptional(ctx)) } + +func TestJWTScopeClaimStrategy(t *testing.T) { + l := logrusx.New("", "") + l.Logrus().SetOutput(io.Discard) + p := MustNew(context.Background(), l) + + ctx := context.Background() + + assert.Equal(t, jwt.JWTScopeFieldList, p.GetJWTScopeField(ctx)) + p.MustSet(ctx, KeyJWTScopeClaimStrategy, "list") + assert.Equal(t, jwt.JWTScopeFieldList, p.GetJWTScopeField(ctx)) + p.MustSet(ctx, KeyJWTScopeClaimStrategy, "string") + assert.Equal(t, jwt.JWTScopeFieldString, p.GetJWTScopeField(ctx)) + p.MustSet(ctx, KeyJWTScopeClaimStrategy, "both") + assert.Equal(t, jwt.JWTScopeFieldBoth, p.GetJWTScopeField(ctx)) +} diff --git a/fositex/config.go b/fositex/config.go index 9336305396b..cc6b7fd686b 100644 --- a/fositex/config.go +++ b/fositex/config.go @@ -189,7 +189,7 @@ func (c *Config) GetAccessTokenIssuer(ctx context.Context) string { } func (c *Config) GetJWTScopeField(ctx context.Context) jwt.JWTScopeFieldEnum { - return jwt.JWTScopeFieldList + return c.deps.Config().GetJWTScopeField(ctx) } func (c *Config) GetFormPostHTMLTemplate(ctx context.Context) *template.Template { diff --git a/spec/config.json b/spec/config.json index 3d63b69a2c4..cbdba33cdfd 100644 --- a/spec/config.json +++ b/spec/config.json @@ -781,6 +781,18 @@ "description": "Defines access token type. jwt is a bad idea, see https://www.ory.sh/docs/hydra/advanced#json-web-tokens", "enum": ["opaque", "jwt"], "default": "opaque" + }, + "jwt": { + "type": "object", + "additionalProperties": false, + "properties": { + "scope_claim": { + "type": "string", + "description": "Defines how the scope claim is represented within a JWT access token", + "enum": ["list", "string", "both"], + "default": "list" + } + } } } },