diff --git a/config.template.yml b/config.template.yml index 7764dc4dc..6dc5a39eb 100644 --- a/config.template.yml +++ b/config.template.yml @@ -1394,15 +1394,9 @@ notifier: ## Sets the client to public. This should typically not be set, please see the documentation for usage. # public: false - ## The policy to require for this client; one_factor or two_factor. - # authorization_policy: two_factor - - ## The consent mode controls how consent is obtained. - # consent_mode: auto - - ## This value controls the duration a consent on this client remains remembered when the consent mode is - ## configured as 'auto' or 'pre-configured'. - # pre_configured_consent_duration: 1w + ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. + # redirect_uris: + # - https://oidc.example.com:8080/oauth2/callback ## Audience this client is allowed to request. # audience: [] @@ -1414,10 +1408,6 @@ notifier: # - email # - profile - ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. - # redirect_uris: - # - https://oidc.example.com:8080/oauth2/callback - ## Grant Types configures which grants this client can obtain. ## It's not recommended to define this unless you know what you're doing. # grant_types: @@ -1435,6 +1425,23 @@ notifier: # - query # - fragment + ## The policy to require for this client; one_factor or two_factor. + # authorization_policy: two_factor + + ## Enforces the use of PKCE for this client when set to true. + # enforce_pkce: false + + ## Enforces the use of PKCE for this client when configured, and enforces the specified challenge method. + ## Options are 'plain' and 'S256'. + # pkce_challenge_method: S256 + ## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256. # userinfo_signing_algorithm: none + + ## The consent mode controls how consent is obtained. + # consent_mode: auto + + ## This value controls the duration a consent on this client remains remembered when the consent mode is + ## configured as 'auto' or 'pre-configured'. + # pre_configured_consent_duration: 1w ... diff --git a/docs/content/en/configuration/identity-providers/open-id-connect.md b/docs/content/en/configuration/identity-providers/open-id-connect.md index 2e0f78e55..462aabc70 100644 --- a/docs/content/en/configuration/identity-providers/open-id-connect.md +++ b/docs/content/en/configuration/identity-providers/open-id-connect.md @@ -404,12 +404,92 @@ useful for SPA's and CLI tools. This option requires setting the [client secret] In addition to the standard rules for redirect URIs, public clients can use the `urn:ietf:wg:oauth:2.0:oob` redirect URI. +#### redirect_uris + +{{< confkey type="list(string)" required="yes" >}} + +A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are +case-sensitive and they differ from application to application - the community has provided +[a list of URL´s for common applications](../../integration/openid-connect/introduction.md). + +Some restrictions that have been placed on clients and +their redirect URIs are as follows: + +1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the + attempt to authorize will fail and an error will be generated. +2. The redirect URIs are case-sensitive. +3. The URI must include a scheme and that scheme must be one of `http` or `https`. +4. The client can ignore rule 3 and use `urn:ietf:wg:oauth:2.0:oob` if it is a [public](#public) client type. + +#### audience + +{{< confkey type="list(string)" required="no" >}} + +A list of audiences this client is allowed to request. + +#### scopes + +{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}} + +A list of scopes to allow this client to consume. See +[scope definitions](../../integration/openid-connect/introduction.md#scope-definitions) for more information. The +documentation for the application you want to use with Authelia will most-likely provide you with the scopes to allow. + +#### grant_types + +{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}} + +A list of grant types this client can return. *It is recommended that this isn't configured at this time unless you +know what you're doing*. Valid options are: `implicit`, `refresh_token`, `authorization_code`, `password`, +`client_credentials`. + +#### response_types + +{{< confkey type="list(string)" default="code" required="no" >}} + +A list of response types this client can return. *It is recommended that this isn't configured at this time unless you +know what you're doing*. Valid options are: `code`, `code id_token`, `id_token`, `token id_token`, `token`, +`token id_token code`. + +#### response_modes + +{{< confkey type="list(string)" default="form_post, query, fragment" required="no" >}} + +A list of response modes this client can return. It is recommended that this isn't configured at this time unless you +know what you're doing. Potential values are `form_post`, `query`, and `fragment`. + #### authorization_policy {{< confkey type="string" default="two_factor" required="no" >}} The authorization policy for this client: either `one_factor` or `two_factor`. +#### enforce_pkce + +{{< confkey type="bool" default="false" required="no" >}} + +This setting enforces the use of [PKCE] for this individual client. To enforce it for all clients see the global +[enforce_pkce](#enforcepkce) setting. + +#### pkce_challenge_method + +{{< confkey type="string" default="" required="no" >}} + +This setting enforces the use of the specified [PKCE] challenge method for this individual client. This setting also +effectively enables the [enforce_pkce](#enforcepkce-1) option for this client. + +Valid values are an empty string, `plain`, or `S256`. It should be noted that `S256` is strongly recommended if the +relying party supports it. + +#### userinfo_signing_algorithm + +{{< confkey type="string" default="none" required="no" >}} + +The algorithm used to sign the userinfo endpoint responses. This can either be `none` or `RS256`. + +See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for +more information. + #### consent_mode {{< confkey type="string" default="auto" required="no" >}} @@ -442,69 +522,6 @@ match exactly with the granted scopes/audience. [consent_mode]: #consentmode -#### audience - -{{< confkey type="list(string)" required="no" >}} - -A list of audiences this client is allowed to request. - -#### scopes - -{{< confkey type="list(string)" default="openid, groups, profile, email" required="no" >}} - -A list of scopes to allow this client to consume. See -[scope definitions](../../integration/openid-connect/introduction.md#scope-definitions) for more information. The -documentation for the application you want to use with Authelia will most-likely provide you with the scopes to allow. - -#### redirect_uris - -{{< confkey type="list(string)" required="yes" >}} - -A list of valid callback URIs this client will redirect to. All other callbacks will be considered unsafe. The URIs are -case-sensitive and they differ from application to application - the community has provided -[a list of URL´s for common applications](../../integration/openid-connect/introduction.md). - -Some restrictions that have been placed on clients and -their redirect URIs are as follows: - -1. If a client attempts to authorize with Authelia and its redirect URI is not listed in the client configuration the - attempt to authorize will fail and an error will be generated. -2. The redirect URIs are case-sensitive. -3. The URI must include a scheme and that scheme must be one of `http` or `https`. -4. The client can ignore rule 3 and use `urn:ietf:wg:oauth:2.0:oob` if it is a [public](#public) client type. - -#### grant_types - -{{< confkey type="list(string)" default="refresh_token, authorization_code" required="no" >}} - -A list of grant types this client can return. *It is recommended that this isn't configured at this time unless you -know what you're doing*. Valid options are: `implicit`, `refresh_token`, `authorization_code`, `password`, -`client_credentials`. - -#### response_types - -{{< confkey type="list(string)" default="code" required="no" >}} - -A list of response types this client can return. *It is recommended that this isn't configured at this time unless you -know what you're doing*. Valid options are: `code`, `code id_token`, `id_token`, `token id_token`, `token`, -`token id_token code`. - -#### response_modes - -{{< confkey type="list(string)" default="form_post, query, fragment" required="no" >}} - -A list of response modes this client can return. It is recommended that this isn't configured at this time unless you -know what you're doing. Potential values are `form_post`, `query`, and `fragment`. - -#### userinfo_signing_algorithm - -{{< confkey type="string" default="none" required="no" >}} - -The algorithm used to sign the userinfo endpoint responses. This can either be `none` or `RS256`. - -See the [integration guide](../../integration/openid-connect/introduction.md#user-information-signing-algorithm) for -more information. - ## Integration To integrate Authelia's [OpenID Connect] implementation with a relying party please see the diff --git a/docs/content/en/reference/guides/templating.md b/docs/content/en/reference/guides/templating.md index 7eb963cdd..b34cc090f 100644 --- a/docs/content/en/reference/guides/templating.md +++ b/docs/content/en/reference/guides/templating.md @@ -2,7 +2,7 @@ title: "Templating" description: "A reference guide on the templates system" lead: "This section contains reference documentation for Authelia's templating capabilities." -date: 2022-12-23T18:31:05+11:00 +date: 2022-12-23T21:58:54+11:00 draft: false images: [] menu: diff --git a/docs/content/en/roadmap/active/openid-connect.md b/docs/content/en/roadmap/active/openid-connect.md index 08cff74f0..72a4785dc 100644 --- a/docs/content/en/roadmap/active/openid-connect.md +++ b/docs/content/en/roadmap/active/openid-connect.md @@ -64,7 +64,7 @@ Feature List: Feature List: -* [Proof Key Code Exchange (PKCE)](https://www.rfc-editor.org/rfc/rfc7636.html) for Authorization Code Flow +* [Proof Key Code Exchange (PKCE)] for Authorization Code Flow * Claims: * `preferred_username` - sending the username in this claim instead of the `sub` claim. @@ -115,8 +115,8 @@ Feature List: {{< roadmap-status stage="in-progress" version="v4.38.0" >}} - * [OAuth 2.0 Pushed Authorization Requests](https://www.rfc-editor.org/rfc/rfc9126.html) +* Per-Client [Proof Key Code Exchange (PKCE)] Policy ### Beta 7 @@ -219,3 +219,4 @@ The `preferred_username` claim was missing and was fixed. [OpenID Connect Core (Subject Identifier Types)]: https://openid.net/specs/openid-connect-core-1_0.html#SubjectIDTypes [OpenID Connect Core (Pairwise Identifier Algorithm)]: https://openid.net/specs/openid-connect-core-1_0.html#PairwiseAlg [OpenID Connect Core (Mandatory to Implement Features for All OpenID Providers)]: https://openid.net/specs/openid-connect-core-1_0.html#ServerMTI +[Proof Key Code Exchange (PKCE)]: https://www.rfc-editor.org/rfc/rfc7636.html diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index 7764dc4dc..6dc5a39eb 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -1394,15 +1394,9 @@ notifier: ## Sets the client to public. This should typically not be set, please see the documentation for usage. # public: false - ## The policy to require for this client; one_factor or two_factor. - # authorization_policy: two_factor - - ## The consent mode controls how consent is obtained. - # consent_mode: auto - - ## This value controls the duration a consent on this client remains remembered when the consent mode is - ## configured as 'auto' or 'pre-configured'. - # pre_configured_consent_duration: 1w + ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. + # redirect_uris: + # - https://oidc.example.com:8080/oauth2/callback ## Audience this client is allowed to request. # audience: [] @@ -1414,10 +1408,6 @@ notifier: # - email # - profile - ## Redirect URI's specifies a list of valid case-sensitive callbacks for this client. - # redirect_uris: - # - https://oidc.example.com:8080/oauth2/callback - ## Grant Types configures which grants this client can obtain. ## It's not recommended to define this unless you know what you're doing. # grant_types: @@ -1435,6 +1425,23 @@ notifier: # - query # - fragment + ## The policy to require for this client; one_factor or two_factor. + # authorization_policy: two_factor + + ## Enforces the use of PKCE for this client when set to true. + # enforce_pkce: false + + ## Enforces the use of PKCE for this client when configured, and enforces the specified challenge method. + ## Options are 'plain' and 'S256'. + # pkce_challenge_method: S256 + ## The algorithm used to sign userinfo endpoint responses for this client, either none or RS256. # userinfo_signing_algorithm: none + + ## The consent mode controls how consent is obtained. + # consent_mode: auto + + ## This value controls the duration a consent on this client remains remembered when the consent mode is + ## configured as 'auto' or 'pre-configured'. + # pre_configured_consent_duration: 1w ... diff --git a/internal/configuration/schema/identity_providers.go b/internal/configuration/schema/identity_providers.go index 04c6f35f9..d56d79f57 100644 --- a/internal/configuration/schema/identity_providers.go +++ b/internal/configuration/schema/identity_providers.go @@ -57,10 +57,13 @@ type OpenIDConnectClientConfiguration struct { ResponseTypes []string `koanf:"response_types"` ResponseModes []string `koanf:"response_modes"` - UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"` - Policy string `koanf:"authorization_policy"` + EnforcePKCE bool `koanf:"enforce_pkce"` + + PKCEChallengeMethod string `koanf:"pkce_challenge_method"` + UserinfoSigningAlgorithm string `koanf:"userinfo_signing_algorithm"` + ConsentMode string `koanf:"consent_mode"` ConsentPreConfiguredDuration *time.Duration `koanf:"pre_configured_consent_duration"` } diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index bcd577f39..07967dc88 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -43,8 +43,10 @@ var Keys = []string{ "identity_providers.oidc.clients[].grant_types", "identity_providers.oidc.clients[].response_types", "identity_providers.oidc.clients[].response_modes", - "identity_providers.oidc.clients[].userinfo_signing_algorithm", "identity_providers.oidc.clients[].authorization_policy", + "identity_providers.oidc.clients[].enforce_pkce", + "identity_providers.oidc.clients[].pkce_challenge_method", + "identity_providers.oidc.clients[].userinfo_signing_algorithm", "identity_providers.oidc.clients[].consent_mode", "identity_providers.oidc.clients[].pre_configured_consent_duration", "authentication_backend.password_reset.disable", diff --git a/internal/configuration/validator/const.go b/internal/configuration/validator/const.go index 014e33583..cf1a8eea0 100644 --- a/internal/configuration/validator/const.go +++ b/internal/configuration/validator/const.go @@ -6,7 +6,6 @@ import ( "github.com/go-webauthn/webauthn/protocol" "github.com/authelia/authelia/v4/internal/configuration/schema" - "github.com/authelia/authelia/v4/internal/oidc" ) @@ -172,6 +171,8 @@ const ( "invalid value: redirect uri '%s' must have the scheme but it is absent" errFmtOIDCClientInvalidPolicy = "identity_providers: oidc: client '%s': option 'policy' must be 'one_factor' " + "or 'two_factor' but it is configured as '%s'" + errFmtOIDCClientInvalidPKCEChallengeMethod = "identity_providers: oidc: client '%s': option 'pkce_challenge_method' must be 'plain' " + + "or 'S256' but it is configured as '%s'" errFmtOIDCClientInvalidConsentMode = "identity_providers: oidc: client '%s': consent: option 'mode' must be one of " + "'%s' but it is configured as '%s'" errFmtOIDCClientInvalidEntry = "identity_providers: oidc: client '%s': option '%s' must only have the values " + diff --git a/internal/configuration/validator/identity_providers.go b/internal/configuration/validator/identity_providers.go index 23a797153..d63f2fb04 100644 --- a/internal/configuration/validator/identity_providers.go +++ b/internal/configuration/validator/identity_providers.go @@ -175,6 +175,13 @@ func validateOIDCClients(config *schema.OpenIDConnectConfiguration, val *schema. val.Push(fmt.Errorf(errFmtOIDCClientInvalidPolicy, client.ID, client.Policy)) } + switch client.PKCEChallengeMethod { + case "", "plain", "S256": + break + default: + val.Push(fmt.Errorf(errFmtOIDCClientInvalidPKCEChallengeMethod, client.ID, client.PKCEChallengeMethod)) + } + validateOIDCClientConsentMode(c, config, val) validateOIDCClientSectorIdentifier(client, val) validateOIDCClientScopes(c, config, val) diff --git a/internal/configuration/validator/identity_providers_test.go b/internal/configuration/validator/identity_providers_test.go index 32118a066..cd3bc0012 100644 --- a/internal/configuration/validator/identity_providers_test.go +++ b/internal/configuration/validator/identity_providers_test.go @@ -331,6 +331,40 @@ func TestShouldRaiseErrorWhenOIDCServerClientBadValues(t *testing.T) { fmt.Sprintf(errFmtOIDCClientInvalidConsentMode, "client-bad-consent-mode", strings.Join(append(validOIDCClientConsentModes, "auto"), "', '"), "cap"), }, }, + { + Name: "InvalidPKCEChallengeMethod", + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "client-bad-pkce-mode", + Secret: MustDecodeSecret("$plaintext$a-secret"), + Policy: policyTwoFactor, + RedirectURIs: []string{ + "https://google.com", + }, + PKCEChallengeMethod: "abc", + }, + }, + Errors: []string{ + fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode", "abc"), + }, + }, + { + Name: "InvalidPKCEChallengeMethodLowerCaseS256", + Clients: []schema.OpenIDConnectClientConfiguration{ + { + ID: "client-bad-pkce-mode-s256", + Secret: MustDecodeSecret("$plaintext$a-secret"), + Policy: policyTwoFactor, + RedirectURIs: []string{ + "https://google.com", + }, + PKCEChallengeMethod: "s256", + }, + }, + Errors: []string{ + fmt.Sprintf(errFmtOIDCClientInvalidPKCEChallengeMethod, "client-bad-pkce-mode-s256", "s256"), + }, + }, } for _, tc := range testCases { @@ -609,7 +643,7 @@ func TestValidateIdentityProvidersShouldRaiseErrorsOnInvalidClientTypes(t *testi assert.EqualError(t, validator.Errors()[1], fmt.Sprintf(errFmtOIDCClientRedirectURIPublic, "client-with-bad-redirect-uri", oauth2InstalledApp)) } -func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *testing.T) { +func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidClientOptions(t *testing.T) { validator := schema.NewStructValidator() config := &schema.IdentityProvidersConfiguration{ OIDC: &schema.OpenIDConnectConfiguration{ @@ -640,6 +674,24 @@ func TestValidateIdentityProvidersShouldNotRaiseErrorsOnValidPublicClients(t *te "http://127.0.0.1", }, }, + { + ID: "client-with-pkce-mode-plain", + Public: true, + Policy: "two_factor", + RedirectURIs: []string{ + "https://pkce.com", + }, + PKCEChallengeMethod: "plain", + }, + { + ID: "client-with-pkce-mode-S256", + Public: true, + Policy: "two_factor", + RedirectURIs: []string{ + "https://pkce.com", + }, + PKCEChallengeMethod: "S256", + }, }, }, } diff --git a/internal/handlers/handler_oidc_authorization.go b/internal/handlers/handler_oidc_authorization.go index 1bf85a25f..73e821927 100644 --- a/internal/handlers/handler_oidc_authorization.go +++ b/internal/handlers/handler_oidc_authorization.go @@ -52,6 +52,16 @@ func OpenIDConnectAuthorization(ctx *middlewares.AutheliaCtx, rw http.ResponseWr return } + if err = client.ValidateAuthorizationPolicy(requester); err != nil { + rfc := fosite.ErrorToRFC6749Error(err) + + ctx.Logger.Errorf("Authorization Request with id '%s' on client with id '%s' failed to validate the authorization policy: %s", requester.GetID(), clientID, rfc.WithExposeDebug(true).GetDescription()) + + ctx.Providers.OpenIDConnect.WriteAuthorizeError(ctx, rw, requester, err) + + return + } + issuer = ctx.RootURL() userSession := ctx.GetSession() diff --git a/internal/oidc/client.go b/internal/oidc/client.go index 61bce5590..633161bc6 100644 --- a/internal/oidc/client.go +++ b/internal/oidc/client.go @@ -1,7 +1,10 @@ package oidc import ( + "fmt" + "github.com/ory/fosite" + "github.com/ory/x/errorsx" "github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authorization" @@ -18,6 +21,10 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) SectorIdentifier: config.SectorIdentifier.String(), Public: config.Public, + EnforcePKCE: config.EnforcePKCE || config.PKCEChallengeMethod != "", + EnforcePKCEChallengeMethod: config.PKCEChallengeMethod != "", + PKCEChallengeMethod: config.PKCEChallengeMethod, + Audience: config.Audience, Scopes: config.Scopes, RedirectURIs: config.RedirectURIs, @@ -39,6 +46,29 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client *Client) return client } +// ValidateAuthorizationPolicy is a helper function to validate additional policy constraints on a per-client basis. +func (c *Client) ValidateAuthorizationPolicy(r fosite.Requester) (err error) { + form := r.GetRequestForm() + + if c.EnforcePKCE { + if form.Get("code_challenge") == "" { + return errorsx.WithStack(fosite.ErrInvalidRequest. + WithHint("Clients must include a code_challenge when performing the authorize code flow, but it is missing."). + WithDebug("The server is configured in a way that enforces PKCE for this client.")) + } + + if c.EnforcePKCEChallengeMethod { + if method := form.Get("code_challenge_method"); method != c.PKCEChallengeMethod { + return errorsx.WithStack(fosite.ErrInvalidRequest. + WithHint(fmt.Sprintf("Client must use code_challenge_method=%s, %s is not allowed.", c.PKCEChallengeMethod, method)). + WithDebug(fmt.Sprintf("The server is configured in a way that enforces PKCE %s as challenge method for this client.", c.PKCEChallengeMethod))) + } + } + } + + return nil +} + // IsAuthenticationLevelSufficient returns if the provided authentication.Level is sufficient for the client of the AutheliaClient. func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) bool { if level == authentication.NotAuthenticated { @@ -48,11 +78,6 @@ func (c *Client) IsAuthenticationLevelSufficient(level authentication.Level) boo return authorization.IsAuthLevelSufficient(level, c.Policy) } -// GetID returns the ID. -func (c *Client) GetID() string { - return c.ID -} - // GetSectorIdentifier returns the SectorIdentifier for this client. func (c *Client) GetSectorIdentifier() string { return c.SectorIdentifier @@ -74,6 +99,11 @@ func (c *Client) GetConsentResponseBody(consent *model.OAuth2ConsentSession) Con return body } +// GetID returns the ID. +func (c *Client) GetID() string { + return c.ID +} + // GetHashedSecret returns the Secret. func (c *Client) GetHashedSecret() []byte { if c.Secret == nil { diff --git a/internal/oidc/client_test.go b/internal/oidc/client_test.go index 1f1a7b860..23b53aaec 100644 --- a/internal/oidc/client_test.go +++ b/internal/oidc/client_test.go @@ -217,6 +217,90 @@ func TestClient_GetResponseTypes(t *testing.T) { assert.Equal(t, "id_token", responseTypes[1]) } +func TestNewClientPKCE(t *testing.T) { + testCases := []struct { + name string + have schema.OpenIDConnectClientConfiguration + expectedEnforcePKCE bool + expectedEnforcePKCEChallengeMethod bool + expected string + req *fosite.Request + err string + }{ + { + "ShouldNotEnforcePKCEAndNotErrorOnNonPKCERequest", + schema.OpenIDConnectClientConfiguration{}, + false, + false, + "", + &fosite.Request{}, + "", + }, + { + "ShouldEnforcePKCEAndErrorOnNonPKCERequest", + schema.OpenIDConnectClientConfiguration{EnforcePKCE: true}, + true, + false, + "", + &fosite.Request{}, + "invalid_request", + }, + { + "ShouldEnforcePKCEAndNotErrorOnPKCERequest", + schema.OpenIDConnectClientConfiguration{EnforcePKCE: true}, + true, + false, + "", + &fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}}}, + "", + }, + {"ShouldEnforcePKCEFromChallengeMethodAndErrorOnNonPKCERequest", + schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"}, + true, + true, + "S256", + &fosite.Request{}, + "invalid_request", + }, + {"ShouldEnforcePKCEFromChallengeMethodAndErrorOnInvalidChallengeMethod", + schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"}, + true, + true, + "S256", + &fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}}}, + "invalid_request", + }, + {"ShouldEnforcePKCEFromChallengeMethodAndNotErrorOnValidRequest", + schema.OpenIDConnectClientConfiguration{PKCEChallengeMethod: "S256"}, + true, + true, + "S256", + &fosite.Request{Form: map[string][]string{"code_challenge": {"abc"}, "code_challenge_method": {"S256"}}}, + "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + client := NewClient(tc.have) + + assert.Equal(t, tc.expectedEnforcePKCE, client.EnforcePKCE) + assert.Equal(t, tc.expectedEnforcePKCEChallengeMethod, client.EnforcePKCEChallengeMethod) + assert.Equal(t, tc.expected, client.PKCEChallengeMethod) + + if tc.req != nil { + err := client.ValidateAuthorizationPolicy(tc.req) + + if tc.err != "" { + assert.EqualError(t, err, tc.err) + } else { + assert.NoError(t, err) + } + } + }) + } +} + func TestClient_IsPublic(t *testing.T) { c := Client{} diff --git a/internal/oidc/types.go b/internal/oidc/types.go index 58328d119..d2782220c 100644 --- a/internal/oidc/types.go +++ b/internal/oidc/types.go @@ -107,6 +107,10 @@ type Client struct { SectorIdentifier string Public bool + EnforcePKCE bool + EnforcePKCEChallengeMethod bool + PKCEChallengeMethod string + Audience []string Scopes []string RedirectURIs []string