From 9fd1709969db7d046642f104a6b27a1ed129b19c Mon Sep 17 00:00:00 2001 From: JarekKa Date: Mon, 11 Dec 2023 14:45:43 +0100 Subject: [PATCH 1/7] feat: add scopes validator for logical evalulation --- .schema/config.schema.json | 16 ++ credentials/scopes_logical_validator.go | 28 ++++ credentials/verifier.go | 13 +- credentials/verifier_default.go | 11 +- credentials/verifier_default_test.go | 196 ++++++++++++++++-------- driver/configuration/provider.go | 2 + driver/configuration/provider_koanf.go | 15 +- pipeline/authn/authenticator_jwt.go | 14 +- 8 files changed, 213 insertions(+), 82 deletions(-) create mode 100644 credentials/scopes_logical_validator.go diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 4dcd8a8032..d4bab3edc2 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -201,6 +201,16 @@ "default": "none", "description": "Sets the strategy validation algorithm." }, + "scopesValidator": { + "title": "Scope Validator", + "type": "string", + "enum": [ + "default", + "any" + ], + "default": "default", + "description": "Sets the strategy verifier algorithm. Default is logical AND and any serves as OR" + }, "configErrorsRedirect": { "type": "object", "title": "HTTP Redirect Error Handler", @@ -604,6 +614,9 @@ "scope_strategy": { "$ref": "#/definitions/scopeStrategy" }, + "scopes_validator": { + "$ref": "#/definitions/scopesValidator" + }, "token_from": { "title": "Token From", "description": "The location of the token.\n If not configured, the token will be received from a default location - 'Authorization' header.\n One and only one location (header or query) must be specified.", @@ -712,6 +725,9 @@ "scope_strategy": { "$ref": "#/definitions/scopeStrategy" }, + "scopes_validator": { + "$ref": "#/definitions/scopesValidator" + }, "pre_authorization": { "title": "Pre-Authorization", "description": "Enable pre-authorization in cases where the OAuth 2.0 Token Introspection endpoint is protected by OAuth 2.0 Bearer Tokens that can be retrieved using the OAuth 2.0 Client Credentials grant.", diff --git a/credentials/scopes_logical_validator.go b/credentials/scopes_logical_validator.go new file mode 100644 index 0000000000..1fbd7efdab --- /dev/null +++ b/credentials/scopes_logical_validator.go @@ -0,0 +1,28 @@ +package credentials + +import ( + "github.com/ory/herodot" + "github.com/pkg/errors" +) + +type ScopesValidator func(scopeResult map[string]bool) error + +func DefaultValidation(scopeResult map[string]bool) error { + for sc, result := range scopeResult { + if !result { + return errors.WithStack(herodot.ErrInternalServerError.WithReasonf(`JSON Web Token is missing required scope "%s".`, sc)) + } + } + + return nil +} + +func AnyValidation(scopeResult map[string]bool) error { + for _, result := range scopeResult { + if result { + return nil + } + } + + return errors.WithStack(herodot.ErrInternalServerError.WithReasonf(`JSON Web Token is missing required scope`)) +} diff --git a/credentials/verifier.go b/credentials/verifier.go index f644821f6d..2d51404272 100644 --- a/credentials/verifier.go +++ b/credentials/verifier.go @@ -25,10 +25,11 @@ type VerifierRegistry interface { } type ValidationContext struct { - Algorithms []string - Issuers []string - Audiences []string - ScopeStrategy fosite.ScopeStrategy - Scope []string - KeyURLs []url.URL + Algorithms []string + Issuers []string + Audiences []string + ScopeStrategy fosite.ScopeStrategy + ScopesValidator ScopesValidator + Scope []string + KeyURLs []url.URL } diff --git a/credentials/verifier_default.go b/credentials/verifier_default.go index c80a8ac70b..c3f270b88a 100644 --- a/credentials/verifier_default.go +++ b/credentials/verifier_default.go @@ -120,11 +120,16 @@ func (v *VerifierDefault) Verify( claims["scp"] = s if r.ScopeStrategy != nil { + scopeResult := make(map[string]bool, len(r.Scope)) + for _, sc := range r.Scope { - if !r.ScopeStrategy(s, sc) { - return nil, herodot.ErrUnauthorized.WithReasonf(`JSON Web Token is missing required scope "%s".`, sc) - } + scopeResult[sc] = r.ScopeStrategy(s, sc) + } + + if err := r.ScopesValidator(scopeResult); err != nil { + return nil, err } + } else { if len(r.Scope) > 0 { return nil, errors.WithStack(helper.ErrRuleFeatureDisabled.WithReason("Scope validation was requested but scope strategy is set to \"none\".")) diff --git a/credentials/verifier_default_test.go b/credentials/verifier_default_test.go index 6701923877..7710af0d8f 100644 --- a/credentials/verifier_default_test.go +++ b/credentials/verifier_default_test.go @@ -46,12 +46,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should pass because JWT is valid", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -68,15 +69,69 @@ func TestVerifierDefault(t *testing.T) { "scp": []string{"scope-3", "scope-2", "scope-1"}, }, }, + { + d: "should pass because one of scopes is valid", + c: &ValidationContext{ + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "not-scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: AnyValidation, + }, + token: sign(jwt.MapClaims{ + "sub": "sub", + "exp": now.Add(time.Hour).Unix(), + "aud": []string{"aud-1", "aud-2"}, + "iss": "iss-2", + "scope": []string{"scope-3", "scope-2", "scope-1"}, + }, "file://../test/stub/jwks-hs.json"), + expectClaims: jwt.MapClaims{ + "sub": "sub", + "exp": float64(now.Add(time.Hour).Unix()), + "aud": []interface{}{"aud-1", "aud-2"}, + "iss": "iss-2", + "scp": []string{"scope-3", "scope-2", "scope-1"}, + }, + }, + { + d: "should fail because one of scopes is invalid and validation is strict", + c: &ValidationContext{ + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "not-scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, + }, + token: sign(jwt.MapClaims{ + "sub": "sub", + "exp": now.Add(time.Hour).Unix(), + "aud": []string{"aud-1", "aud-2"}, + "iss": "iss-2", + "scope": []string{"scope-3", "scope-2", "scope-1"}, + }, "file://../test/stub/jwks-hs.json"), + expectClaims: jwt.MapClaims{ + "sub": "sub", + "exp": float64(now.Add(time.Hour).Unix()), + "aud": []interface{}{"aud-1", "aud-2"}, + "iss": "iss-2", + "scp": []string{"scope-3", "scope-2", "scope-1"}, + }, + expectErr: true, + }, { d: "should pass even when scope is a string", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -96,12 +151,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should pass when scope is keyed as scp", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -121,12 +177,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should pass when scope is keyed as scopes", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -164,12 +221,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should fail when algorithm does not match", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-rsa-single.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-rsa-single.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -183,12 +241,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should fail when audience mismatches", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -202,12 +261,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should fail when issuer mismatches", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -221,12 +281,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should fail when issuer mismatches", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -240,12 +301,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should fail when expired", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -259,12 +321,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should fail when nbf in future", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -279,12 +342,13 @@ func TestVerifierDefault(t *testing.T) { { d: "should fail when iat in future", c: &ValidationContext{ - Algorithms: []string{"HS256"}, - Audiences: []string{"aud-1", "aud-2"}, - Issuers: []string{"iss-1", "iss-2"}, - Scope: []string{"scope-1", "scope-2"}, - KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, - ScopeStrategy: fosite.ExactScopeStrategy, + Algorithms: []string{"HS256"}, + Audiences: []string{"aud-1", "aud-2"}, + Issuers: []string{"iss-1", "iss-2"}, + Scope: []string{"scope-1", "scope-2"}, + KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, + ScopeStrategy: fosite.ExactScopeStrategy, + ScopesValidator: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 4293d97092..89cb99a94c 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -5,6 +5,7 @@ package configuration import ( "encoding/json" + "github.com/ory/oathkeeper/credentials" "net/url" "testing" "time" @@ -70,6 +71,7 @@ type Provider interface { PrometheusHideRequestPaths() bool PrometheusCollapseRequestPaths() bool + ToScopesValidation(value string, key string) credentials.ScopesValidator ToScopeStrategy(value string, key string) fosite.ScopeStrategy ParseURLs(sources []string) ([]url.URL, error) JSONWebKeyURLs() []string diff --git a/driver/configuration/provider_koanf.go b/driver/configuration/provider_koanf.go index 35795f03d6..f77136491d 100644 --- a/driver/configuration/provider_koanf.go +++ b/driver/configuration/provider_koanf.go @@ -9,6 +9,8 @@ import ( "crypto/sha256" "encoding/json" "fmt" + "github.com/knadh/koanf/v2" + "github.com/ory/oathkeeper/credentials" "net/url" "strings" "sync" @@ -18,7 +20,6 @@ import ( "github.com/dgraph-io/ristretto" "github.com/google/uuid" - "github.com/knadh/koanf/v2" "github.com/pkg/errors" "github.com/rs/cors" "github.com/spf13/pflag" @@ -268,6 +269,18 @@ func (v *KoanfProvider) getURL(value string, key string) *url.URL { return u } +func (v *KoanfProvider) ToScopesValidation(value string, key string) credentials.ScopesValidator { + switch strings.ToLower(value) { + case "default": + return credentials.DefaultValidation + case "any": + return credentials.AnyValidation + default: + v.l.Errorf(`Configuration key "%s" declares unknown scope strategy "%s", only "default", "any", are supported. Falling back to strategy "default".`, key, value) + return credentials.DefaultValidation + } +} + func (v *KoanfProvider) ToScopeStrategy(value string, key string) fosite.ScopeStrategy { switch s := stringsx.SwitchExact(strings.ToLower(value)); { case s.AddCase("hierarchic"): diff --git a/pipeline/authn/authenticator_jwt.go b/pipeline/authn/authenticator_jwt.go index 8852efc0d4..80e9acbfa5 100644 --- a/pipeline/authn/authenticator_jwt.go +++ b/pipeline/authn/authenticator_jwt.go @@ -34,6 +34,7 @@ type AuthenticatorOAuth2JWTConfiguration struct { AllowedAlgorithms []string `json:"allowed_algorithms"` JWKSURLs []string `json:"jwks_urls"` ScopeStrategy string `json:"scope_strategy"` + ScopesValidator string `json:"scopes_validator"` BearerTokenLocation *helper.BearerTokenLocation `json:"token_from"` } @@ -105,12 +106,13 @@ func (a *AuthenticatorJWT) Authenticate(r *http.Request, session *Authentication } pt, err := a.r.CredentialsVerifier().Verify(r.Context(), token, &credentials.ValidationContext{ - Algorithms: cf.AllowedAlgorithms, - KeyURLs: jwksu, - Scope: cf.Scope, - Issuers: cf.Issuers, - Audiences: cf.Audience, - ScopeStrategy: a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.jwt.Config.scope_strategy"), + Algorithms: cf.AllowedAlgorithms, + KeyURLs: jwksu, + Scope: cf.Scope, + Issuers: cf.Issuers, + Audiences: cf.Audience, + ScopeStrategy: a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.jwt.Config.scope_strategy"), + ScopesValidator: a.c.ToScopesValidation(cf.ScopesValidator, "authenticators.jwt.Config.scopes_validator"), }) if err != nil { de := herodot.ToDefaultError(err, "") From 636b35eb9e31534019c1d1651757c8cdfc433216 Mon Sep 17 00:00:00 2001 From: JarekKa Date: Mon, 11 Dec 2023 15:26:08 +0100 Subject: [PATCH 2/7] goimports --- driver/configuration/provider.go | 3 ++- driver/configuration/provider_koanf.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 89cb99a94c..4b5dd6841f 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -5,11 +5,12 @@ package configuration import ( "encoding/json" - "github.com/ory/oathkeeper/credentials" "net/url" "testing" "time" + "github.com/ory/oathkeeper/credentials" + "github.com/rs/cors" "github.com/ory/fosite" diff --git a/driver/configuration/provider_koanf.go b/driver/configuration/provider_koanf.go index f77136491d..bfe602c705 100644 --- a/driver/configuration/provider_koanf.go +++ b/driver/configuration/provider_koanf.go @@ -9,14 +9,15 @@ import ( "crypto/sha256" "encoding/json" "fmt" - "github.com/knadh/koanf/v2" - "github.com/ory/oathkeeper/credentials" "net/url" "strings" "sync" "testing" "time" + "github.com/knadh/koanf/v2" + "github.com/ory/oathkeeper/credentials" + "github.com/dgraph-io/ristretto" "github.com/google/uuid" From 8eb3f0e15e0bb649173878c7634aafacd5da1eed Mon Sep 17 00:00:00 2001 From: JarekKa Date: Tue, 12 Dec 2023 12:13:21 +0100 Subject: [PATCH 3/7] make format --- .schema/config.schema.json | 5 +---- credentials/scopes_logical_validator.go | 6 +++++- driver/configuration/provider_koanf.go | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.schema/config.schema.json b/.schema/config.schema.json index d4bab3edc2..4ddd7fd233 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -204,10 +204,7 @@ "scopesValidator": { "title": "Scope Validator", "type": "string", - "enum": [ - "default", - "any" - ], + "enum": ["default", "any"], "default": "default", "description": "Sets the strategy verifier algorithm. Default is logical AND and any serves as OR" }, diff --git a/credentials/scopes_logical_validator.go b/credentials/scopes_logical_validator.go index 1fbd7efdab..8081e69ab8 100644 --- a/credentials/scopes_logical_validator.go +++ b/credentials/scopes_logical_validator.go @@ -1,8 +1,12 @@ +// Copyright © 2023 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + package credentials import ( - "github.com/ory/herodot" "github.com/pkg/errors" + + "github.com/ory/herodot" ) type ScopesValidator func(scopeResult map[string]bool) error diff --git a/driver/configuration/provider_koanf.go b/driver/configuration/provider_koanf.go index bfe602c705..7bc70edb8b 100644 --- a/driver/configuration/provider_koanf.go +++ b/driver/configuration/provider_koanf.go @@ -16,6 +16,7 @@ import ( "time" "github.com/knadh/koanf/v2" + "github.com/ory/oathkeeper/credentials" "github.com/dgraph-io/ristretto" From ec8385f6c241cf879396c5e15559a6f798e3918b Mon Sep 17 00:00:00 2001 From: JarekKa Date: Tue, 12 Dec 2023 13:42:58 +0100 Subject: [PATCH 4/7] increase coverage --- .../configuration/provider_koanf_public_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/driver/configuration/provider_koanf_public_test.go b/driver/configuration/provider_koanf_public_test.go index 50cbf91309..efa841f947 100644 --- a/driver/configuration/provider_koanf_public_test.go +++ b/driver/configuration/provider_koanf_public_test.go @@ -389,6 +389,21 @@ func TestKoanfProvider(t *testing.T) { }) } +func TestToScopesValidation(t *testing.T) { + p, err := configuration.NewKoanfProvider( + context.Background(), + nil, + logrusx.New("", ""), + configx.WithConfigFiles("./../../internal/config/.oathkeeper.yaml"), + ) + require.NoError(t, err) + + assert.Nil(t, p.ToScopesValidation("default", "foo")(map[string]bool{"foo": true})) + assert.NotNil(t, p.ToScopesValidation("default", "foo")(map[string]bool{"foo": true, "bar": false})) + assert.Nil(t, p.ToScopesValidation("any", "foo")(map[string]bool{"foo": true, "bar": false})) + assert.NotNil(t, p.ToScopesValidation("whatever", "foo")(map[string]bool{"foo": true, "bar": false})) +} + func TestToScopeStrategy(t *testing.T) { p, err := configuration.NewKoanfProvider( context.Background(), From de27dca4242fd17728ab433f8c32a720760dbc1e Mon Sep 17 00:00:00 2001 From: JarekKa Date: Tue, 12 Dec 2023 13:51:11 +0100 Subject: [PATCH 5/7] increase coverage --- driver/configuration/provider_koanf_public_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/driver/configuration/provider_koanf_public_test.go b/driver/configuration/provider_koanf_public_test.go index efa841f947..57ccf63e3c 100644 --- a/driver/configuration/provider_koanf_public_test.go +++ b/driver/configuration/provider_koanf_public_test.go @@ -401,7 +401,9 @@ func TestToScopesValidation(t *testing.T) { assert.Nil(t, p.ToScopesValidation("default", "foo")(map[string]bool{"foo": true})) assert.NotNil(t, p.ToScopesValidation("default", "foo")(map[string]bool{"foo": true, "bar": false})) assert.Nil(t, p.ToScopesValidation("any", "foo")(map[string]bool{"foo": true, "bar": false})) + assert.NotNil(t, p.ToScopesValidation("any", "foo")(map[string]bool{})) assert.NotNil(t, p.ToScopesValidation("whatever", "foo")(map[string]bool{"foo": true, "bar": false})) + } func TestToScopeStrategy(t *testing.T) { From cb968617a0d5abc4bbaca3522c6d533735054d2b Mon Sep 17 00:00:00 2001 From: JarekKa Date: Wed, 13 Dec 2023 09:51:28 +0100 Subject: [PATCH 6/7] rename scopes validator to scope validation --- .schema/config.schema.json | 12 ++++----- credentials/scopes_logical_validator.go | 2 +- credentials/verifier.go | 2 +- credentials/verifier_default.go | 2 +- credentials/verifier_default_test.go | 26 +++++++++---------- driver/configuration/provider.go | 2 +- driver/configuration/provider_koanf.go | 2 +- .../provider_koanf_public_test.go | 12 ++++----- pipeline/authn/authenticator_jwt.go | 4 +-- 9 files changed, 32 insertions(+), 32 deletions(-) diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 4ddd7fd233..23f847e1dd 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -201,8 +201,8 @@ "default": "none", "description": "Sets the strategy validation algorithm." }, - "scopesValidator": { - "title": "Scope Validator", + "scopeValidation": { + "title": "Scope Validation", "type": "string", "enum": ["default", "any"], "default": "default", @@ -611,8 +611,8 @@ "scope_strategy": { "$ref": "#/definitions/scopeStrategy" }, - "scopes_validator": { - "$ref": "#/definitions/scopesValidator" + "scope_validation": { + "$ref": "#/definitions/ScopeValidation" }, "token_from": { "title": "Token From", @@ -722,8 +722,8 @@ "scope_strategy": { "$ref": "#/definitions/scopeStrategy" }, - "scopes_validator": { - "$ref": "#/definitions/scopesValidator" + "scope_validation": { + "$ref": "#/definitions/scopeValidation" }, "pre_authorization": { "title": "Pre-Authorization", diff --git a/credentials/scopes_logical_validator.go b/credentials/scopes_logical_validator.go index 8081e69ab8..b2057ba78c 100644 --- a/credentials/scopes_logical_validator.go +++ b/credentials/scopes_logical_validator.go @@ -9,7 +9,7 @@ import ( "github.com/ory/herodot" ) -type ScopesValidator func(scopeResult map[string]bool) error +type ScopeValidation func(scopeResult map[string]bool) error func DefaultValidation(scopeResult map[string]bool) error { for sc, result := range scopeResult { diff --git a/credentials/verifier.go b/credentials/verifier.go index 2d51404272..0b19901c9c 100644 --- a/credentials/verifier.go +++ b/credentials/verifier.go @@ -29,7 +29,7 @@ type ValidationContext struct { Issuers []string Audiences []string ScopeStrategy fosite.ScopeStrategy - ScopesValidator ScopesValidator + ScopeValidation ScopeValidation Scope []string KeyURLs []url.URL } diff --git a/credentials/verifier_default.go b/credentials/verifier_default.go index c3f270b88a..b7573ef597 100644 --- a/credentials/verifier_default.go +++ b/credentials/verifier_default.go @@ -126,7 +126,7 @@ func (v *VerifierDefault) Verify( scopeResult[sc] = r.ScopeStrategy(s, sc) } - if err := r.ScopesValidator(scopeResult); err != nil { + if err := r.ScopeValidation(scopeResult); err != nil { return nil, err } diff --git a/credentials/verifier_default_test.go b/credentials/verifier_default_test.go index 7710af0d8f..dd67a041db 100644 --- a/credentials/verifier_default_test.go +++ b/credentials/verifier_default_test.go @@ -52,7 +52,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -78,7 +78,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "not-scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: AnyValidation, + ScopeValidation: AnyValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -104,7 +104,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "not-scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -131,7 +131,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -157,7 +157,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -183,7 +183,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -227,7 +227,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-rsa-single.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -247,7 +247,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -267,7 +267,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -287,7 +287,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -307,7 +307,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -327,7 +327,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", @@ -348,7 +348,7 @@ func TestVerifierDefault(t *testing.T) { Scope: []string{"scope-1", "scope-2"}, KeyURLs: []url.URL{*x.ParseURLOrPanic("file://../test/stub/jwks-hs.json")}, ScopeStrategy: fosite.ExactScopeStrategy, - ScopesValidator: DefaultValidation, + ScopeValidation: DefaultValidation, }, token: sign(jwt.MapClaims{ "sub": "sub", diff --git a/driver/configuration/provider.go b/driver/configuration/provider.go index 4b5dd6841f..18234deb55 100644 --- a/driver/configuration/provider.go +++ b/driver/configuration/provider.go @@ -72,7 +72,7 @@ type Provider interface { PrometheusHideRequestPaths() bool PrometheusCollapseRequestPaths() bool - ToScopesValidation(value string, key string) credentials.ScopesValidator + ToScopeValidation(value string, key string) credentials.ScopeValidation ToScopeStrategy(value string, key string) fosite.ScopeStrategy ParseURLs(sources []string) ([]url.URL, error) JSONWebKeyURLs() []string diff --git a/driver/configuration/provider_koanf.go b/driver/configuration/provider_koanf.go index 7bc70edb8b..f1ab8dfc50 100644 --- a/driver/configuration/provider_koanf.go +++ b/driver/configuration/provider_koanf.go @@ -271,7 +271,7 @@ func (v *KoanfProvider) getURL(value string, key string) *url.URL { return u } -func (v *KoanfProvider) ToScopesValidation(value string, key string) credentials.ScopesValidator { +func (v *KoanfProvider) ToScopeValidation(value string, key string) credentials.ScopeValidation { switch strings.ToLower(value) { case "default": return credentials.DefaultValidation diff --git a/driver/configuration/provider_koanf_public_test.go b/driver/configuration/provider_koanf_public_test.go index 57ccf63e3c..0fb474585a 100644 --- a/driver/configuration/provider_koanf_public_test.go +++ b/driver/configuration/provider_koanf_public_test.go @@ -389,7 +389,7 @@ func TestKoanfProvider(t *testing.T) { }) } -func TestToScopesValidation(t *testing.T) { +func TestToScopeValidation(t *testing.T) { p, err := configuration.NewKoanfProvider( context.Background(), nil, @@ -398,11 +398,11 @@ func TestToScopesValidation(t *testing.T) { ) require.NoError(t, err) - assert.Nil(t, p.ToScopesValidation("default", "foo")(map[string]bool{"foo": true})) - assert.NotNil(t, p.ToScopesValidation("default", "foo")(map[string]bool{"foo": true, "bar": false})) - assert.Nil(t, p.ToScopesValidation("any", "foo")(map[string]bool{"foo": true, "bar": false})) - assert.NotNil(t, p.ToScopesValidation("any", "foo")(map[string]bool{})) - assert.NotNil(t, p.ToScopesValidation("whatever", "foo")(map[string]bool{"foo": true, "bar": false})) + assert.Nil(t, p.ToScopeValidation("default", "foo")(map[string]bool{"foo": true})) + assert.NotNil(t, p.ToScopeValidation("default", "foo")(map[string]bool{"foo": true, "bar": false})) + assert.Nil(t, p.ToScopeValidation("any", "foo")(map[string]bool{"foo": true, "bar": false})) + assert.NotNil(t, p.ToScopeValidation("any", "foo")(map[string]bool{})) + assert.NotNil(t, p.ToScopeValidation("whatever", "foo")(map[string]bool{"foo": true, "bar": false})) } diff --git a/pipeline/authn/authenticator_jwt.go b/pipeline/authn/authenticator_jwt.go index 80e9acbfa5..a5fea11f27 100644 --- a/pipeline/authn/authenticator_jwt.go +++ b/pipeline/authn/authenticator_jwt.go @@ -34,7 +34,7 @@ type AuthenticatorOAuth2JWTConfiguration struct { AllowedAlgorithms []string `json:"allowed_algorithms"` JWKSURLs []string `json:"jwks_urls"` ScopeStrategy string `json:"scope_strategy"` - ScopesValidator string `json:"scopes_validator"` + ScopeValidation string `json:"scope_validation"` BearerTokenLocation *helper.BearerTokenLocation `json:"token_from"` } @@ -112,7 +112,7 @@ func (a *AuthenticatorJWT) Authenticate(r *http.Request, session *Authentication Issuers: cf.Issuers, Audiences: cf.Audience, ScopeStrategy: a.c.ToScopeStrategy(cf.ScopeStrategy, "authenticators.jwt.Config.scope_strategy"), - ScopesValidator: a.c.ToScopesValidation(cf.ScopesValidator, "authenticators.jwt.Config.scopes_validator"), + ScopeValidation: a.c.ToScopeValidation(cf.ScopeValidation, "authenticators.jwt.Config.scope_validation"), }) if err != nil { de := herodot.ToDefaultError(err, "") From c096e04f92be4c7bdb59685e193a99cd12bac427 Mon Sep 17 00:00:00 2001 From: JarekKa Date: Fri, 15 Dec 2023 15:20:52 +0100 Subject: [PATCH 7/7] Fix typos --- credentials/scopes_logical_validator.go | 2 +- driver/configuration/provider_koanf.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/credentials/scopes_logical_validator.go b/credentials/scopes_logical_validator.go index b2057ba78c..00fa739dc7 100644 --- a/credentials/scopes_logical_validator.go +++ b/credentials/scopes_logical_validator.go @@ -14,7 +14,7 @@ type ScopeValidation func(scopeResult map[string]bool) error func DefaultValidation(scopeResult map[string]bool) error { for sc, result := range scopeResult { if !result { - return errors.WithStack(herodot.ErrInternalServerError.WithReasonf(`JSON Web Token is missing required scope "%s".`, sc)) + return errors.WithStack(herodot.ErrInternalServerError.WithReasonf(`JSON Web Token is missing required scope "%s"`, sc)) } } diff --git a/driver/configuration/provider_koanf.go b/driver/configuration/provider_koanf.go index f1ab8dfc50..26307aafbb 100644 --- a/driver/configuration/provider_koanf.go +++ b/driver/configuration/provider_koanf.go @@ -278,7 +278,7 @@ func (v *KoanfProvider) ToScopeValidation(value string, key string) credentials. case "any": return credentials.AnyValidation default: - v.l.Errorf(`Configuration key "%s" declares unknown scope strategy "%s", only "default", "any", are supported. Falling back to strategy "default".`, key, value) + v.l.Errorf(`Configuration key "%s" declares unknown scope validation policy "%s", only "default" and "any" are supported. Falling back to policy "default".`, key, value) return credentials.DefaultValidation } }