feat(oidc): client_secret_jwt client auth (#5253)
This adds the authentication machinery for the client_secret_jwt to the Default Client Authentication Strategy. Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/5279/head
parent
c729d33da6
commit
1dbfbc5f88
|
@ -1517,6 +1517,10 @@ notifier:
|
||||||
## The permitted client authentication method for the Token Endpoint for this client.
|
## The permitted client authentication method for the Token Endpoint for this client.
|
||||||
# token_endpoint_auth_method: 'client_secret_basic'
|
# token_endpoint_auth_method: 'client_secret_basic'
|
||||||
|
|
||||||
|
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
|
||||||
|
## the 'client_secret_jwt' token_endpoint_auth_method.
|
||||||
|
# token_endpoint_auth_signing_alg: HS256
|
||||||
|
|
||||||
## The policy to require for this client; one_factor or two_factor.
|
## The policy to require for this client; one_factor or two_factor.
|
||||||
# authorization_policy: 'two_factor'
|
# authorization_policy: 'two_factor'
|
||||||
|
|
||||||
|
|
|
@ -555,11 +555,18 @@ more information.
|
||||||
The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined
|
The registered client authentication mechanism used by this client for the [Token Endpoint]. If no method is defined
|
||||||
the confidential client type will accept any supported method. The public client type defaults to `none` as this
|
the confidential client type will accept any supported method. The public client type defaults to `none` as this
|
||||||
is required by the specification. This may be required as a breaking change in future versions.
|
is required by the specification. This may be required as a breaking change in future versions.
|
||||||
Supported values are `client_secret_basic`, `client_secret_post`, and `none`.
|
Supported values are `client_secret_basic`, `client_secret_post`, `client_secret_jwt`, and `none`.
|
||||||
|
|
||||||
See the [integration guide](../../integration/openid-connect/introduction.md#client-authentication-method) for
|
See the [integration guide](../../integration/openid-connect/introduction.md#client-authentication-method) for
|
||||||
more information.
|
more information.
|
||||||
|
|
||||||
|
#### token_endpoint_auth_signing_alg
|
||||||
|
|
||||||
|
{{< confkey type="string" default="HS256" required="no" >}}
|
||||||
|
|
||||||
|
The JWT signing algorithm accepted when the [token_endpoint_auth_method](#tokenendpointauthmethod) is configured as
|
||||||
|
`client_secret_jwt`. Supported values are `HS256`, `HS385`, and `HS512`.
|
||||||
|
|
||||||
#### consent_mode
|
#### consent_mode
|
||||||
|
|
||||||
{{< confkey type="string" default="auto" required="no" >}}
|
{{< confkey type="string" default="auto" required="no" >}}
|
||||||
|
|
|
@ -174,7 +174,7 @@ specification and the [OAuth 2.0 - Client Types] specification for more informat
|
||||||
|:------------------------------------:|:-----------------------------:|:----------------------:|:-----------------------:|:--------------------------------------------------------:|
|
|:------------------------------------:|:-----------------------------:|:----------------------:|:-----------------------:|:--------------------------------------------------------:|
|
||||||
| Secret via HTTP Basic Auth Scheme | `client_secret_basic` | `confidential` | N/A | N/A |
|
| Secret via HTTP Basic Auth Scheme | `client_secret_basic` | `confidential` | N/A | N/A |
|
||||||
| Secret via HTTP POST Body | `client_secret_post` | `confidential` | N/A | N/A |
|
| Secret via HTTP POST Body | `client_secret_post` | `confidential` | N/A | N/A |
|
||||||
| JWT (signed by secret) | `client_secret_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
|
| JWT (signed by secret) | `client_secret_jwt` | `confidential` | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
|
||||||
| JWT (signed by private key) | `private_key_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
|
| JWT (signed by private key) | `private_key_jwt` | Not Supported | N/A | `urn:ietf:params:oauth:client-assertion-type:jwt-bearer` |
|
||||||
| [OAuth 2.0 Mutual-TLS] | `tls_client_auth` | Not Supported | N/A | N/A |
|
| [OAuth 2.0 Mutual-TLS] | `tls_client_auth` | Not Supported | N/A | N/A |
|
||||||
| [OAuth 2.0 Mutual-TLS] (Self Signed) | `self_signed_tls_client_auth` | Not Supported | N/A | N/A |
|
| [OAuth 2.0 Mutual-TLS] (Self Signed) | `self_signed_tls_client_auth` | Not Supported | N/A | N/A |
|
||||||
|
|
|
@ -1517,6 +1517,10 @@ notifier:
|
||||||
## The permitted client authentication method for the Token Endpoint for this client.
|
## The permitted client authentication method for the Token Endpoint for this client.
|
||||||
# token_endpoint_auth_method: 'client_secret_basic'
|
# token_endpoint_auth_method: 'client_secret_basic'
|
||||||
|
|
||||||
|
## The permitted client authentication signing algorithm for the Token Endpoint for this client when using
|
||||||
|
## the 'client_secret_jwt' token_endpoint_auth_method.
|
||||||
|
# token_endpoint_auth_signing_alg: HS256
|
||||||
|
|
||||||
## The policy to require for this client; one_factor or two_factor.
|
## The policy to require for this client; one_factor or two_factor.
|
||||||
# authorization_policy: 'two_factor'
|
# authorization_policy: 'two_factor'
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,8 @@ type OpenIDConnectClientConfiguration struct {
|
||||||
ResponseTypes []string `koanf:"response_types"`
|
ResponseTypes []string `koanf:"response_types"`
|
||||||
ResponseModes []string `koanf:"response_modes"`
|
ResponseModes []string `koanf:"response_modes"`
|
||||||
|
|
||||||
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
|
TokenEndpointAuthMethod string `koanf:"token_endpoint_auth_method"`
|
||||||
|
TokenEndpointAuthSigningAlg string `koanf:"token_endpoint_auth_signing_alg"`
|
||||||
|
|
||||||
Policy string `koanf:"authorization_policy"`
|
Policy string `koanf:"authorization_policy"`
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ var Keys = []string{
|
||||||
"identity_providers.oidc.clients[].response_types",
|
"identity_providers.oidc.clients[].response_types",
|
||||||
"identity_providers.oidc.clients[].response_modes",
|
"identity_providers.oidc.clients[].response_modes",
|
||||||
"identity_providers.oidc.clients[].token_endpoint_auth_method",
|
"identity_providers.oidc.clients[].token_endpoint_auth_method",
|
||||||
|
"identity_providers.oidc.clients[].token_endpoint_auth_signing_alg",
|
||||||
"identity_providers.oidc.clients[].authorization_policy",
|
"identity_providers.oidc.clients[].authorization_policy",
|
||||||
"identity_providers.oidc.clients[].enforce_par",
|
"identity_providers.oidc.clients[].enforce_par",
|
||||||
"identity_providers.oidc.clients[].enforce_pkce",
|
"identity_providers.oidc.clients[].enforce_pkce",
|
||||||
|
|
|
@ -161,9 +161,10 @@ const (
|
||||||
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s"
|
errFmtOIDCClientsWithEmptyID = "identity_providers: oidc: clients: option 'id' is required but was absent on the clients in positions %s"
|
||||||
errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors"
|
errFmtOIDCClientsDeprecated = "identity_providers: oidc: clients: warnings for clients above indicate deprecated functionality and it's strongly suggested these issues are checked and fixed if they're legitimate issues or reported if they are not as in a future version these warnings will become errors"
|
||||||
|
|
||||||
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
|
errFmtOIDCClientInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is required"
|
||||||
errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable"
|
errFmtOIDCClientInvalidSecretPlainText = "identity_providers: oidc: client '%s': option 'secret' is plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable"
|
||||||
errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is " +
|
errFmtOIDCClientInvalidSecretNotPlainText = "identity_providers: oidc: client '%s': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'"
|
||||||
|
errFmtOIDCClientPublicInvalidSecret = "identity_providers: oidc: client '%s': option 'secret' is " +
|
||||||
"required to be empty when option 'public' is true"
|
"required to be empty when option 'public' is true"
|
||||||
errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
|
errFmtOIDCClientRedirectURICantBeParsed = "identity_providers: oidc: client '%s': option 'redirect_uris' has an " +
|
||||||
"invalid value: redirect uri '%s' could not be parsed: %v"
|
"invalid value: redirect uri '%s' could not be parsed: %v"
|
||||||
|
@ -183,6 +184,8 @@ const (
|
||||||
"'token_endpoint_auth_method' must be one of %s when configured as the confidential client type unless it only includes implicit flow response types such as %s but it's configured as '%s'"
|
"'token_endpoint_auth_method' must be one of %s when configured as the confidential client type unless it only includes implicit flow response types such as %s but it's configured as '%s'"
|
||||||
errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: client '%s': option " +
|
errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic = "identity_providers: oidc: client '%s': option " +
|
||||||
"'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as '%s'"
|
"'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as '%s'"
|
||||||
|
errFmtOIDCClientInvalidTokenEndpointAuthSigAlg = "identity_providers: oidc: client '%s': option " +
|
||||||
|
"'token_endpoint_auth_signing_alg' must be %s when option 'token_endpoint_auth_method' is %s"
|
||||||
errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " +
|
errFmtOIDCClientInvalidSectorIdentifier = "identity_providers: oidc: client '%s': option " +
|
||||||
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'"
|
"'sector_identifier' with value '%s': must be a URL with only the host component for example '%s' but it has a %s with the value '%s'"
|
||||||
errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " +
|
errFmtOIDCClientInvalidSectorIdentifierWithoutValue = "identity_providers: oidc: client '%s': option " +
|
||||||
|
@ -413,7 +416,7 @@ var (
|
||||||
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
|
validOIDCCORSEndpoints = []string{oidc.EndpointAuthorization, oidc.EndpointPushedAuthorizationRequest, oidc.EndpointToken, oidc.EndpointIntrospection, oidc.EndpointRevocation, oidc.EndpointUserinfo}
|
||||||
|
|
||||||
validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
|
validOIDCClientScopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile, oidc.ScopeGroups, oidc.ScopeOfflineAccess}
|
||||||
validOIDCClientUserinfoAlgorithms = []string{oidc.SigningAlgorithmNone, oidc.SigningAlgorithmRSAWithSHA256}
|
validOIDCClientUserinfoAlgorithms = []string{oidc.SigningAlgNone, oidc.SigningAlgRSAUsingSHA256}
|
||||||
validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
|
validOIDCClientConsentModes = []string{auto, oidc.ClientConsentModeImplicit.String(), oidc.ClientConsentModeExplicit.String(), oidc.ClientConsentModePreConfigured.String()}
|
||||||
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
|
validOIDCClientResponseModes = []string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery, oidc.ResponseModeFragment}
|
||||||
validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
validOIDCClientResponseTypes = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
||||||
|
@ -422,8 +425,9 @@ var (
|
||||||
validOIDCClientResponseTypesRefreshToken = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
validOIDCClientResponseTypesRefreshToken = []string{oidc.ResponseTypeAuthorizationCodeFlow, oidc.ResponseTypeHybridFlowIDToken, oidc.ResponseTypeHybridFlowToken, oidc.ResponseTypeHybridFlowBoth}
|
||||||
validOIDCClientGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode}
|
validOIDCClientGrantTypes = []string{oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeAuthorizationCode}
|
||||||
|
|
||||||
validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
|
validOIDCClientTokenEndpointAuthMethods = []string{oidc.ClientAuthMethodNone, oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic, oidc.ClientAuthMethodClientSecretJWT}
|
||||||
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
|
validOIDCClientTokenEndpointAuthMethodsConfidential = []string{oidc.ClientAuthMethodClientSecretPost, oidc.ClientAuthMethodClientSecretBasic}
|
||||||
|
validOIDCClientTokenEndpointAuthSigAlgs = []string{oidc.SigningAlgHMACUsingSHA256, oidc.SigningAlgHMACUsingSHA384, oidc.SigningAlgHMACUsingSHA512}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -193,8 +193,13 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s
|
||||||
} else {
|
} else {
|
||||||
if config.Clients[c].Secret == nil {
|
if config.Clients[c].Secret == nil {
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, config.Clients[c].ID))
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecret, config.Clients[c].ID))
|
||||||
} else if config.Clients[c].Secret.IsPlainText() {
|
} else {
|
||||||
val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, config.Clients[c].ID))
|
switch {
|
||||||
|
case config.Clients[c].Secret.IsPlainText() && config.Clients[c].TokenEndpointAuthMethod != oidc.ClientAuthMethodClientSecretJWT:
|
||||||
|
val.PushWarning(fmt.Errorf(errFmtOIDCClientInvalidSecretPlainText, config.Clients[c].ID))
|
||||||
|
case !config.Clients[c].Secret.IsPlainText() && config.Clients[c].TokenEndpointAuthMethod == oidc.ClientAuthMethodClientSecretJWT:
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidSecretNotPlainText, config.Clients[c].ID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +227,7 @@ func validateOIDCClient(c int, config *schema.OpenIDConnectConfiguration, val *s
|
||||||
validateOIDCClientGrantTypes(c, config, val, errDeprecatedFunc)
|
validateOIDCClientGrantTypes(c, config, val, errDeprecatedFunc)
|
||||||
validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc)
|
validateOIDCClientRedirectURIs(c, config, val, errDeprecatedFunc)
|
||||||
|
|
||||||
validateOIDCClientTokenEndpointAuthMethod(c, config, val)
|
validateOIDCClientTokenEndpointAuth(c, config, val)
|
||||||
validateOIDDClientUserinfoAlgorithm(c, config, val)
|
validateOIDDClientUserinfoAlgorithm(c, config, val)
|
||||||
|
|
||||||
validateOIDCClientSectorIdentifier(c, config, val)
|
validateOIDCClientSectorIdentifier(c, config, val)
|
||||||
|
@ -481,13 +486,9 @@ func validateOIDCClientRedirectURIs(c int, config *schema.OpenIDConnectConfigura
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDCClientTokenEndpointAuthMethod(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDCClientTokenEndpointAuth(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||||
implcit := len(config.Clients[c].ResponseTypes) != 0 && utils.IsStringSliceContainsAll(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesImplicitFlow)
|
implcit := len(config.Clients[c].ResponseTypes) != 0 && utils.IsStringSliceContainsAll(config.Clients[c].ResponseTypes, validOIDCClientResponseTypesImplicitFlow)
|
||||||
|
|
||||||
if config.Clients[c].TokenEndpointAuthMethod == "" && (config.Clients[c].Public || implcit) {
|
|
||||||
config.Clients[c].TokenEndpointAuthMethod = oidc.ClientAuthMethodNone
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case config.Clients[c].TokenEndpointAuthMethod == "":
|
case config.Clients[c].TokenEndpointAuthMethod == "":
|
||||||
break
|
break
|
||||||
|
@ -501,6 +502,18 @@ func validateOIDCClientTokenEndpointAuthMethod(c int, config *schema.OpenIDConne
|
||||||
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic,
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic,
|
||||||
config.Clients[c].ID, config.Clients[c].TokenEndpointAuthMethod))
|
config.Clients[c].ID, config.Clients[c].TokenEndpointAuthMethod))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch config.Clients[c].TokenEndpointAuthMethod {
|
||||||
|
case "":
|
||||||
|
break
|
||||||
|
case oidc.ClientAuthMethodClientSecretJWT:
|
||||||
|
switch {
|
||||||
|
case config.Clients[c].TokenEndpointAuthSigningAlg == "":
|
||||||
|
config.Clients[c].TokenEndpointAuthSigningAlg = oidc.SigningAlgHMACUsingSHA256
|
||||||
|
case !utils.IsStringInSlice(config.Clients[c].TokenEndpointAuthSigningAlg, validOIDCClientTokenEndpointAuthSigAlgs):
|
||||||
|
val.Push(fmt.Errorf(errFmtOIDCClientInvalidTokenEndpointAuthSigAlg, config.Clients[c].ID, strJoinOr(validOIDCClientTokenEndpointAuthSigAlgs), config.Clients[c].TokenEndpointAuthMethod))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
func validateOIDDClientUserinfoAlgorithm(c int, config *schema.OpenIDConnectConfiguration, val *schema.StructValidator) {
|
||||||
|
|
|
@ -758,7 +758,7 @@ func TestValidateIdentityProvidersShouldRaiseWarningOnPlainTextClients(t *testin
|
||||||
assert.Len(t, validator.Errors(), 0)
|
assert.Len(t, validator.Errors(), 0)
|
||||||
require.Len(t, validator.Warnings(), 1)
|
require.Len(t, validator.Warnings(), 1)
|
||||||
|
|
||||||
assert.EqualError(t, validator.Warnings()[0], "identity_providers: oidc: client 'client-with-invalid-secret_standard': option 'secret' is plaintext but it should be a hashed value as plaintext values are deprecated and will be removed when oidc becomes stable")
|
assert.EqualError(t, validator.Warnings()[0], "identity_providers: oidc: client 'client-with-invalid-secret_standard': option 'secret' is plaintext but for clients not using the 'token_endpoint_auth_method' of 'client_secret_jwt' it should be a hashed value as plaintext values are deprecated with the exception of 'client_secret_jwt' and will be removed when oidc becomes stable")
|
||||||
}
|
}
|
||||||
|
|
||||||
// All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
|
// All valid schemes are supported as defined in https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
|
||||||
|
@ -1445,51 +1445,6 @@ func TestValidateOIDCClients(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ShouldSetDefaultTokenEndpointClientAuthMethodPublicClientType",
|
|
||||||
func(have *schema.OpenIDConnectConfiguration) {
|
|
||||||
have.Clients[0].Public = true
|
|
||||||
have.Clients[0].Secret = nil
|
|
||||||
},
|
|
||||||
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
|
||||||
assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod)
|
|
||||||
},
|
|
||||||
tcv{
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
tcv{
|
|
||||||
[]string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
|
|
||||||
[]string{oidc.ResponseTypeAuthorizationCodeFlow},
|
|
||||||
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
|
|
||||||
[]string{oidc.GrantTypeAuthorizationCode},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ShouldSetDefaultTokenEndpointClientAuthMethodConfidentialClientTypeImplicitFlow",
|
|
||||||
nil,
|
|
||||||
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
|
||||||
assert.Equal(t, oidc.ClientAuthMethodNone, have.Clients[0].TokenEndpointAuthMethod)
|
|
||||||
},
|
|
||||||
tcv{
|
|
||||||
nil,
|
|
||||||
[]string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
tcv{
|
|
||||||
[]string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
|
|
||||||
[]string{oidc.ResponseTypeImplicitFlowIDToken, oidc.ResponseTypeImplicitFlowToken, oidc.ResponseTypeImplicitFlowBoth},
|
|
||||||
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeFragment},
|
|
||||||
[]string{oidc.GrantTypeImplicit},
|
|
||||||
},
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ShouldNotOverrideValidClientAuthMethod",
|
"ShouldNotOverrideValidClientAuthMethod",
|
||||||
func(have *schema.OpenIDConnectConfiguration) {
|
func(have *schema.OpenIDConnectConfiguration) {
|
||||||
|
@ -1535,7 +1490,7 @@ func TestValidateOIDCClients(t *testing.T) {
|
||||||
},
|
},
|
||||||
nil,
|
nil,
|
||||||
[]string{
|
[]string{
|
||||||
"identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', or 'client_secret_basic' but it's configured as 'client_credentials'",
|
"identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', 'client_secret_basic', or 'client_secret_jwt' but it's configured as 'client_credentials'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1619,7 +1574,7 @@ func TestValidateOIDCClients(t *testing.T) {
|
||||||
"ShouldSetDefaultUserInfoAlg",
|
"ShouldSetDefaultUserInfoAlg",
|
||||||
nil,
|
nil,
|
||||||
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
||||||
assert.Equal(t, oidc.SigningAlgorithmNone, have.Clients[0].UserinfoSigningAlgorithm)
|
assert.Equal(t, oidc.SigningAlgNone, have.Clients[0].UserinfoSigningAlgorithm)
|
||||||
},
|
},
|
||||||
tcv{
|
tcv{
|
||||||
nil,
|
nil,
|
||||||
|
@ -1639,10 +1594,10 @@ func TestValidateOIDCClients(t *testing.T) {
|
||||||
{
|
{
|
||||||
"ShouldNotOverrideUserInfoAlg",
|
"ShouldNotOverrideUserInfoAlg",
|
||||||
func(have *schema.OpenIDConnectConfiguration) {
|
func(have *schema.OpenIDConnectConfiguration) {
|
||||||
have.Clients[0].UserinfoSigningAlgorithm = oidc.SigningAlgorithmRSAWithSHA256
|
have.Clients[0].UserinfoSigningAlgorithm = oidc.SigningAlgRSAUsingSHA256
|
||||||
},
|
},
|
||||||
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
||||||
assert.Equal(t, oidc.SigningAlgorithmRSAWithSHA256, have.Clients[0].UserinfoSigningAlgorithm)
|
assert.Equal(t, oidc.SigningAlgRSAUsingSHA256, have.Clients[0].UserinfoSigningAlgorithm)
|
||||||
},
|
},
|
||||||
tcv{
|
tcv{
|
||||||
nil,
|
nil,
|
||||||
|
@ -1827,6 +1782,131 @@ func TestValidateOIDCClients(t *testing.T) {
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"ShouldRaiseErrorOnIncorrectlyConfiguredTokenEndpointClientAuthMethodClientSecretJWT",
|
||||||
|
func(have *schema.OpenIDConnectConfiguration) {
|
||||||
|
have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretJWT
|
||||||
|
have.Clients[0].Secret = MustDecodeSecret("$pbkdf2-sha512$310000$c8p78n7pUMln0jzvd4aK4Q$JNRBzwAo0ek5qKn50cFzzvE9RXV88h1wJn5KGiHrD0YKtZaR/nCb2CJPOsKaPK0hjf.9yHxzQGZziziccp6Yng")
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
tcv{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
tcv{
|
||||||
|
[]string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
|
||||||
|
[]string{oidc.ResponseTypeAuthorizationCodeFlow},
|
||||||
|
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
|
||||||
|
[]string{oidc.GrantTypeAuthorizationCode},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[]string{
|
||||||
|
"identity_providers: oidc: client 'test': option 'secret' must be plaintext with option 'token_endpoint_auth_method' with a value of 'client_secret_jwt'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldNotRaiseWarningOrErrorOnCorrectlyConfiguredTokenEndpointClientAuthMethodClientSecretJWT",
|
||||||
|
func(have *schema.OpenIDConnectConfiguration) {
|
||||||
|
have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretJWT
|
||||||
|
have.Clients[0].Secret = MustDecodeSecret("$plaintext$abc123")
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
tcv{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
tcv{
|
||||||
|
[]string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
|
||||||
|
[]string{oidc.ResponseTypeAuthorizationCodeFlow},
|
||||||
|
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
|
||||||
|
[]string{oidc.GrantTypeAuthorizationCode},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldSetDefaultTokenEndpointAuthSigAlg",
|
||||||
|
func(have *schema.OpenIDConnectConfiguration) {
|
||||||
|
have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretJWT
|
||||||
|
have.Clients[0].Secret = MustDecodeSecret("$plaintext$abc123")
|
||||||
|
},
|
||||||
|
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
||||||
|
assert.Equal(t, oidc.SigningAlgHMACUsingSHA256, have.Clients[0].TokenEndpointAuthSigningAlg)
|
||||||
|
},
|
||||||
|
tcv{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
tcv{
|
||||||
|
[]string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
|
||||||
|
[]string{oidc.ResponseTypeAuthorizationCodeFlow},
|
||||||
|
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
|
||||||
|
[]string{oidc.GrantTypeAuthorizationCode},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldRaiseErrorOnInvalidPublicTokenAuthAlg",
|
||||||
|
func(have *schema.OpenIDConnectConfiguration) {
|
||||||
|
have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretJWT
|
||||||
|
have.Clients[0].TokenEndpointAuthSigningAlg = oidc.SigningAlgHMACUsingSHA256
|
||||||
|
have.Clients[0].Secret = nil
|
||||||
|
have.Clients[0].Public = true
|
||||||
|
},
|
||||||
|
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
||||||
|
assert.Equal(t, oidc.SigningAlgHMACUsingSHA256, have.Clients[0].TokenEndpointAuthSigningAlg)
|
||||||
|
},
|
||||||
|
tcv{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
tcv{
|
||||||
|
[]string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
|
||||||
|
[]string{oidc.ResponseTypeAuthorizationCodeFlow},
|
||||||
|
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
|
||||||
|
[]string{oidc.GrantTypeAuthorizationCode},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[]string{
|
||||||
|
"identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be 'none' when configured as the public client type but it's configured as 'client_secret_jwt'",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ShouldRaiseErrorOnInvalidTokenAuthAlgClientTypeConfidential",
|
||||||
|
func(have *schema.OpenIDConnectConfiguration) {
|
||||||
|
have.Clients[0].TokenEndpointAuthMethod = oidc.ClientAuthMethodClientSecretJWT
|
||||||
|
have.Clients[0].TokenEndpointAuthSigningAlg = "abcinvalid"
|
||||||
|
have.Clients[0].Secret = MustDecodeSecret("$plaintext$abc123")
|
||||||
|
},
|
||||||
|
func(t *testing.T, have *schema.OpenIDConnectConfiguration) {
|
||||||
|
assert.Equal(t, "abcinvalid", have.Clients[0].TokenEndpointAuthSigningAlg)
|
||||||
|
},
|
||||||
|
tcv{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
tcv{
|
||||||
|
[]string{oidc.ScopeOpenID, oidc.ScopeGroups, oidc.ScopeProfile, oidc.ScopeEmail},
|
||||||
|
[]string{oidc.ResponseTypeAuthorizationCodeFlow},
|
||||||
|
[]string{oidc.ResponseModeFormPost, oidc.ResponseModeQuery},
|
||||||
|
[]string{oidc.GrantTypeAuthorizationCode},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[]string{
|
||||||
|
"identity_providers: oidc: client 'test': option 'token_endpoint_auth_signing_alg' must be 'HS256', 'HS384', or 'HS512' when option 'token_endpoint_auth_method' is client_secret_jwt",
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
errDeprecatedFunc := func() {}
|
errDeprecatedFunc := func() {}
|
||||||
|
@ -1891,10 +1971,9 @@ func TestValidateOIDCClientTokenEndpointAuthMethod(t *testing.T) {
|
||||||
errs []string
|
errs []string
|
||||||
}{
|
}{
|
||||||
{"ShouldSetDefaultValueConfidential", "", false, "", nil},
|
{"ShouldSetDefaultValueConfidential", "", false, "", nil},
|
||||||
{"ShouldSetDefaultValuePublic", "", true, oidc.ClientAuthMethodNone, nil},
|
|
||||||
{"ShouldErrorOnInvalidValue", "abc", false, "abc",
|
{"ShouldErrorOnInvalidValue", "abc", false, "abc",
|
||||||
[]string{
|
[]string{
|
||||||
"identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', or 'client_secret_basic' but it's configured as 'abc'",
|
"identity_providers: oidc: client 'test': option 'token_endpoint_auth_method' must be one of 'none', 'client_secret_post', 'client_secret_basic', or 'client_secret_jwt' but it's configured as 'abc'",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{"ShouldErrorOnInvalidValueForPublicClient", "client_secret_post", true, "client_secret_post",
|
{"ShouldErrorOnInvalidValueForPublicClient", "client_secret_post", true, "client_secret_post",
|
||||||
|
@ -1923,7 +2002,7 @@ func TestValidateOIDCClientTokenEndpointAuthMethod(t *testing.T) {
|
||||||
|
|
||||||
val := schema.NewStructValidator()
|
val := schema.NewStructValidator()
|
||||||
|
|
||||||
validateOIDCClientTokenEndpointAuthMethod(0, have, val)
|
validateOIDCClientTokenEndpointAuth(0, have, val)
|
||||||
|
|
||||||
assert.Equal(t, tc.expected, have.Clients[0].TokenEndpointAuthMethod)
|
assert.Equal(t, tc.expected, have.Clients[0].TokenEndpointAuthMethod)
|
||||||
assert.Len(t, val.Warnings(), 0)
|
assert.Len(t, val.Warnings(), 0)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -100,7 +100,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims)
|
ctx.Logger.Tracef("UserInfo Response with id '%s' on client with id '%s' is being sent with the following claims: %+v", requester.GetID(), clientID, claims)
|
||||||
|
|
||||||
switch client.GetUserinfoSigningAlgorithm() {
|
switch client.GetUserinfoSigningAlgorithm() {
|
||||||
case oidc.SigningAlgorithmRSAWithSHA256:
|
case oidc.SigningAlgRSAUsingSHA256:
|
||||||
var jti uuid.UUID
|
var jti uuid.UUID
|
||||||
|
|
||||||
if jti, err = uuid.NewRandom(); err != nil {
|
if jti, err = uuid.NewRandom(); err != nil {
|
||||||
|
@ -126,7 +126,7 @@ func OpenIDConnectUserinfo(ctx *middlewares.AutheliaCtx, rw http.ResponseWriter,
|
||||||
|
|
||||||
rw.Header().Set(fasthttp.HeaderContentType, "application/jwt")
|
rw.Header().Set(fasthttp.HeaderContentType, "application/jwt")
|
||||||
_, _ = rw.Write([]byte(token))
|
_, _ = rw.Write([]byte(token))
|
||||||
case oidc.SigningAlgorithmNone, "":
|
case oidc.SigningAlgNone, "":
|
||||||
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
ctx.Providers.OpenIDConnect.Write(rw, req, claims)
|
||||||
default:
|
default:
|
||||||
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.GetUserinfoSigningAlgorithm())))
|
ctx.Providers.OpenIDConnect.WriteError(rw, req, errors.WithStack(fosite.ErrServerError.WithHintf("Unsupported UserInfo signing algorithm '%s'.", client.GetUserinfoSigningAlgorithm())))
|
||||||
|
|
|
@ -47,8 +47,9 @@ func NewClient(config schema.OpenIDConnectClientConfiguration) (client Client) {
|
||||||
|
|
||||||
if config.TokenEndpointAuthMethod != "" && config.TokenEndpointAuthMethod != "auto" {
|
if config.TokenEndpointAuthMethod != "" && config.TokenEndpointAuthMethod != "auto" {
|
||||||
client = &FullClient{
|
client = &FullClient{
|
||||||
BaseClient: base,
|
BaseClient: base,
|
||||||
TokenEndpointAuthMethod: config.TokenEndpointAuthMethod,
|
TokenEndpointAuthMethod: config.TokenEndpointAuthMethod,
|
||||||
|
TokenEndpointAuthSigningAlgorithm: config.TokenEndpointAuthSigningAlg,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
client = base
|
client = base
|
||||||
|
@ -133,7 +134,7 @@ func (c *BaseClient) GetResponseModes() []fosite.ResponseModeType {
|
||||||
// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm.
|
// GetUserinfoSigningAlgorithm returns the UserinfoSigningAlgorithm.
|
||||||
func (c *BaseClient) GetUserinfoSigningAlgorithm() string {
|
func (c *BaseClient) GetUserinfoSigningAlgorithm() string {
|
||||||
if c.UserinfoSigningAlgorithm == "" {
|
if c.UserinfoSigningAlgorithm == "" {
|
||||||
c.UserinfoSigningAlgorithm = SigningAlgorithmNone
|
c.UserinfoSigningAlgorithm = SigningAlgNone
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.UserinfoSigningAlgorithm
|
return c.UserinfoSigningAlgorithm
|
||||||
|
@ -306,7 +307,7 @@ func (c *FullClient) GetTokenEndpointAuthMethod() string {
|
||||||
// authentication methods.
|
// authentication methods.
|
||||||
func (c *FullClient) GetTokenEndpointAuthSigningAlgorithm() string {
|
func (c *FullClient) GetTokenEndpointAuthSigningAlgorithm() string {
|
||||||
if c.TokenEndpointAuthSigningAlgorithm == "" {
|
if c.TokenEndpointAuthSigningAlgorithm == "" {
|
||||||
c.TokenEndpointAuthSigningAlgorithm = SigningAlgorithmRSAWithSHA256
|
c.TokenEndpointAuthSigningAlgorithm = SigningAlgRSAUsingSHA256
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.TokenEndpointAuthSigningAlgorithm
|
return c.TokenEndpointAuthSigningAlgorithm
|
||||||
|
|
|
@ -0,0 +1,331 @@
|
||||||
|
package oidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rsa"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-crypt/crypt/algorithm/plaintext"
|
||||||
|
"github.com/golang-jwt/jwt/v4"
|
||||||
|
"github.com/ory/fosite"
|
||||||
|
"github.com/ory/x/errorsx"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultClientAuthenticationStrategy is a copy of fosite's with the addition of the client_secret_jwt method and some
|
||||||
|
// minor superficial changes.
|
||||||
|
//
|
||||||
|
//nolint:gocyclo // Complexity is necessary to remain in feature parity.
|
||||||
|
func (p *OpenIDConnectProvider) DefaultClientAuthenticationStrategy(ctx context.Context, r *http.Request, form url.Values) (client fosite.Client, err error) {
|
||||||
|
if assertionType := form.Get(FormParameterClientAssertionType); assertionType == ClientAssertionJWTBearerType {
|
||||||
|
assertion := form.Get(FormParameterClientAssertion)
|
||||||
|
if len(assertion) == 0 {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("The client_assertion request parameter must be set when using client_assertion_type of '%s'.", ClientAssertionJWTBearerType))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
token *jwt.Token
|
||||||
|
clientID string
|
||||||
|
)
|
||||||
|
|
||||||
|
token, err = jwt.ParseWithClaims(assertion, jwt.MapClaims{}, func(t *jwt.Token) (any, error) {
|
||||||
|
clientID, _, err = clientCredentialsFromRequestBody(form, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if clientID == "" {
|
||||||
|
claims := t.Claims.(jwt.MapClaims)
|
||||||
|
|
||||||
|
if sub, ok := claims[ClaimSubject].(string); !ok {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("The claim 'sub' from the client_assertion JSON Web Token is undefined."))
|
||||||
|
} else {
|
||||||
|
clientID = sub
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if client, err = p.Store.GetClient(ctx, clientID); err != nil {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithWrap(err).WithDebug(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
oidcClient, ok := client.(*FullClient)
|
||||||
|
if !ok {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("The client configuration does not support OpenID Connect specific authentication methods."))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch oidcClient.GetTokenEndpointAuthMethod() {
|
||||||
|
case ClientAuthMethodPrivateKeyJWT, ClientAuthMethodClientSecretJWT:
|
||||||
|
break
|
||||||
|
case ClientAuthMethodNone:
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("This requested OAuth 2.0 client does not support client authentication, however 'client_assertion' was provided in the request."))
|
||||||
|
case ClientAuthMethodClientSecretPost:
|
||||||
|
fallthrough
|
||||||
|
case ClientAuthMethodClientSecretBasic:
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however 'client_assertion' was provided in the request.", oidcClient.GetTokenEndpointAuthMethod()))
|
||||||
|
default:
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however that method is not supported by this server.", oidcClient.GetTokenEndpointAuthMethod()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if oidcClient.GetTokenEndpointAuthSigningAlgorithm() != fmt.Sprintf("%s", t.Header[JWTHeaderKeyAlgorithm]) {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The 'client_assertion' uses signing algorithm '%s' but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header[JWTHeaderKeyAlgorithm], oidcClient.GetTokenEndpointAuthSigningAlgorithm()))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t.Method {
|
||||||
|
case jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512:
|
||||||
|
return p.findClientPublicJWK(ctx, oidcClient, t, true)
|
||||||
|
case jwt.SigningMethodES256, jwt.SigningMethodES384, jwt.SigningMethodES512:
|
||||||
|
return p.findClientPublicJWK(ctx, oidcClient, t, false)
|
||||||
|
case jwt.SigningMethodPS256, jwt.SigningMethodPS384, jwt.SigningMethodPS512:
|
||||||
|
return p.findClientPublicJWK(ctx, oidcClient, t, true)
|
||||||
|
case jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512:
|
||||||
|
if spd, ok := oidcClient.Secret.(*schema.PasswordDigest); ok {
|
||||||
|
if secret, ok := spd.Digest.(*plaintext.Digest); ok {
|
||||||
|
return secret.Key(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("This client does not support authentication method 'client_secret_jwt' as the client secret is not in plaintext."))
|
||||||
|
default:
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The 'client_assertion' request parameter uses unsupported signing algorithm '%s'.", t.Header[JWTHeaderKeyAlgorithm]))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
var r *fosite.RFC6749Error
|
||||||
|
|
||||||
|
if errors.As(err, &r) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var e *jwt.ValidationError
|
||||||
|
|
||||||
|
if errors.As(err, &e) {
|
||||||
|
rfc := fosite.ErrInvalidClient.WithHint("Unable to verify the integrity of the 'client_assertion' value.").WithWrap(err)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case e.Errors&jwt.ValidationErrorMalformed != 0:
|
||||||
|
return nil, errorsx.WithStack(rfc.WithDebug("The token is malformed."))
|
||||||
|
case e.Errors&jwt.ValidationErrorIssuedAt != 0:
|
||||||
|
return nil, errorsx.WithStack(rfc.WithDebug("The token was used before it was issued."))
|
||||||
|
case e.Errors&jwt.ValidationErrorExpired != 0:
|
||||||
|
return nil, errorsx.WithStack(rfc.WithDebug("The token is expired."))
|
||||||
|
case e.Errors&jwt.ValidationErrorNotValidYet != 0:
|
||||||
|
return nil, errorsx.WithStack(rfc.WithDebug("The token isn't valid yet."))
|
||||||
|
case e.Errors&jwt.ValidationErrorSignatureInvalid != 0:
|
||||||
|
return nil, errorsx.WithStack(rfc.WithDebug("The signature is invalid."))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("Unable to verify the integrity of the 'client_assertion' value.").WithWrap(err).WithDebug(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, err
|
||||||
|
} else if err = token.Claims.Valid(); err != nil {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("Unable to verify the request object because its claims could not be validated, check if the expiry time is set correctly.").WithWrap(err).WithDebug(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := token.Claims.(jwt.MapClaims)
|
||||||
|
|
||||||
|
tokenURL := p.Config.GetTokenURL(ctx)
|
||||||
|
|
||||||
|
var jti string
|
||||||
|
|
||||||
|
if !claims.VerifyIssuer(clientID, true) {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("Claim 'iss' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client."))
|
||||||
|
} else if tokenURL == "" {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrMisconfiguration.WithHint("The authorization server's token endpoint URL has not been set."))
|
||||||
|
} else if sub, ok := claims[ClaimSubject].(string); !ok || sub != clientID {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("Claim 'sub' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client."))
|
||||||
|
} else if jti, ok = claims[ClaimJWTID].(string); !ok || len(jti) == 0 {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("Claim 'jti' from 'client_assertion' must be set but is not."))
|
||||||
|
} else if p.Store.ClientAssertionJWTValid(ctx, jti) != nil {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrJTIKnown.WithHint("Claim 'jti' from 'client_assertion' MUST only be used once."))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
|
||||||
|
var expiry int64
|
||||||
|
|
||||||
|
switch exp := claims[ClaimExpirationTime].(type) {
|
||||||
|
case float64:
|
||||||
|
expiry = int64(exp)
|
||||||
|
case int64:
|
||||||
|
expiry = exp
|
||||||
|
case json.Number:
|
||||||
|
expiry, err = exp.Int64()
|
||||||
|
default:
|
||||||
|
err = fosite.ErrInvalidClient.WithHint("Unable to type assert the expiry time from claims. This should not happen as we validate the expiry time already earlier with token.Claims.Valid()")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errorsx.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = p.Store.SetClientAssertionJWT(ctx, jti, time.Unix(expiry, 0)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
|
||||||
|
if auds, ok := claims[ClaimAudience].([]any); ok {
|
||||||
|
for _, aud := range auds {
|
||||||
|
if a, ok := aud.(string); ok && a == tokenURL {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", tokenURL))
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
} else if len(assertionType) > 0 {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("Unknown client_assertion_type '%s'.", assertionType))
|
||||||
|
}
|
||||||
|
|
||||||
|
clientID, clientSecret, err := clientCredentialsFromRequest(r, form)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if client, err = p.Store.GetClient(ctx, clientID); err != nil {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithWrap(err).WithDebug(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
if oidcClient, ok := client.(fosite.OpenIDConnectClient); ok {
|
||||||
|
method := oidcClient.GetTokenEndpointAuthMethod()
|
||||||
|
|
||||||
|
if form.Get(FormParameterClientID) != "" && form.Get(FormParameterClientSecret) != "" && method != ClientAuthMethodClientSecretPost {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'client_secret_post' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_post'.", method))
|
||||||
|
} else if _, secret, basicOk := r.BasicAuth(); basicOk && secret != "" && method != ClientAuthMethodClientSecretBasic {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'client_secret_basic' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_basic'.", method))
|
||||||
|
} else if method != ClientAuthMethodNone && client.IsPublic() {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'none' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'none'.", method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.IsPublic() {
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = p.checkClientSecret(ctx, client, []byte(clientSecret)); err != nil {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithWrap(err).WithDebug(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OpenIDConnectProvider) checkClientSecret(ctx context.Context, client fosite.Client, clientSecret []byte) (err error) {
|
||||||
|
if err = p.Config.GetSecretsHasher(ctx).Compare(ctx, client.GetHashedSecret(), clientSecret); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cc, ok := client.(fosite.ClientWithSecretRotation)
|
||||||
|
if !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hash := range cc.GetRotatedHashes() {
|
||||||
|
if err = p.Config.GetSecretsHasher(ctx).Compare(ctx, hash, clientSecret); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *OpenIDConnectProvider) findClientPublicJWK(ctx context.Context, oidcClient fosite.OpenIDConnectClient, t *jwt.Token, expectsRSAKey bool) (any, error) {
|
||||||
|
if set := oidcClient.GetJSONWebKeys(); set != nil {
|
||||||
|
return findPublicKey(t, set, expectsRSAKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if location := oidcClient.GetJSONWebKeysURI(); len(location) > 0 {
|
||||||
|
keys, err := p.Config.GetJWKSFetcherStrategy(ctx).Resolve(ctx, location, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if key, err := findPublicKey(t, keys, expectsRSAKey); err == nil {
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
keys, err = p.Config.GetJWKSFetcherStrategy(ctx).Resolve(ctx, location, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return findPublicKey(t, keys, expectsRSAKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidClient.WithHint("The OAuth 2.0 Client has no JSON Web Keys set registered, but they are needed to complete the request."))
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPublicKey(t *jwt.Token, set *jose.JSONWebKeySet, expectsRSAKey bool) (any, error) {
|
||||||
|
keys := set.Keys
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("The retrieved JSON Web Key Set does not contain any key."))
|
||||||
|
}
|
||||||
|
|
||||||
|
kid, ok := t.Header[JWTHeaderKeyIdentifier].(string)
|
||||||
|
if ok {
|
||||||
|
keys = set.Key(kid)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("The JSON Web Token uses signing key with kid '%s', which could not be found.", kid))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
if key.Use != KeyUseSignature {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectsRSAKey {
|
||||||
|
if k, ok := key.Key.(*rsa.PublicKey); ok {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if k, ok := key.Key.(*ecdsa.PublicKey); ok {
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if expectsRSAKey {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("Unable to find RSA public key with use='sig' for kid '%s' in JSON Web Key Set.", kid))
|
||||||
|
} else {
|
||||||
|
return nil, errorsx.WithStack(fosite.ErrInvalidRequest.WithHintf("Unable to find ECDSA public key with use='sig' for kid '%s' in JSON Web Key Set.", kid))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientCredentialsFromRequest(r *http.Request, form url.Values) (clientID, clientSecret string, err error) {
|
||||||
|
if id, secret, ok := r.BasicAuth(); !ok {
|
||||||
|
return clientCredentialsFromRequestBody(form, true)
|
||||||
|
} else if clientID, err = url.QueryUnescape(id); err != nil {
|
||||||
|
return "", "", errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("The client id in the HTTP authorization header could not be decoded from 'application/x-www-form-urlencoded'.").WithWrap(err).WithDebug(err.Error()))
|
||||||
|
} else if clientSecret, err = url.QueryUnescape(secret); err != nil {
|
||||||
|
return "", "", errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("The client secret in the HTTP authorization header could not be decoded from 'application/x-www-form-urlencoded'.").WithWrap(err).WithDebug(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientID, clientSecret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientCredentialsFromRequestBody(form url.Values, forceID bool) (clientID, clientSecret string, err error) {
|
||||||
|
clientID = form.Get(FormParameterClientID)
|
||||||
|
clientSecret = form.Get(FormParameterClientSecret)
|
||||||
|
|
||||||
|
if clientID == "" && forceID {
|
||||||
|
return "", "", errorsx.WithStack(fosite.ErrInvalidRequest.WithHint("Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body."))
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientID, clientSecret, nil
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ func TestNewClient(t *testing.T) {
|
||||||
bclient, ok := client.(*BaseClient)
|
bclient, ok := client.(*BaseClient)
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
|
assert.Equal(t, "", bclient.UserinfoSigningAlgorithm)
|
||||||
assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
|
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlgorithm())
|
||||||
|
|
||||||
_, ok = client.(*FullClient)
|
_, ok = client.(*FullClient)
|
||||||
assert.False(t, ok)
|
assert.False(t, ok)
|
||||||
|
@ -64,9 +64,9 @@ func TestNewClient(t *testing.T) {
|
||||||
assert.Equal(t, "", fclient.UserinfoSigningAlgorithm)
|
assert.Equal(t, "", fclient.UserinfoSigningAlgorithm)
|
||||||
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
|
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.TokenEndpointAuthMethod)
|
||||||
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
|
assert.Equal(t, ClientAuthMethodClientSecretBasic, fclient.GetTokenEndpointAuthMethod())
|
||||||
assert.Equal(t, SigningAlgorithmNone, client.GetUserinfoSigningAlgorithm())
|
assert.Equal(t, SigningAlgNone, client.GetUserinfoSigningAlgorithm())
|
||||||
assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm)
|
assert.Equal(t, "", fclient.TokenEndpointAuthSigningAlgorithm)
|
||||||
assert.Equal(t, SigningAlgorithmRSAWithSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
|
assert.Equal(t, SigningAlgRSAUsingSHA256, fclient.GetTokenEndpointAuthSigningAlgorithm())
|
||||||
assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
|
assert.Equal(t, "", fclient.RequestObjectSigningAlgorithm)
|
||||||
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
|
assert.Equal(t, "", fclient.GetRequestObjectSigningAlgorithm())
|
||||||
assert.Equal(t, "", fclient.JSONWebKeysURI)
|
assert.Equal(t, "", fclient.JSONWebKeysURI)
|
||||||
|
@ -545,11 +545,3 @@ func TestClient_IsPublic(t *testing.T) {
|
||||||
c.Public = true
|
c.Public = true
|
||||||
assert.True(t, c.IsPublic())
|
assert.True(t, c.IsPublic())
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustDecodeSecret(value string) *schema.PasswordDigest {
|
|
||||||
if secret, err := schema.DecodePasswordDigest(value); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
return secret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -40,6 +40,11 @@ const (
|
||||||
ClaimClientIdentifier = "client_id"
|
ClaimClientIdentifier = "client_id"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ClientAssertionJWTBearerType is the JWT bearer assertion.
|
||||||
|
ClientAssertionJWTBearerType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" //nolint:gosec // False Positive.
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
lifespanTokenDefault = time.Hour
|
lifespanTokenDefault = time.Hour
|
||||||
lifespanRefreshTokenDefault = time.Hour * 24 * 30
|
lifespanRefreshTokenDefault = time.Hour * 24 * 30
|
||||||
|
@ -75,6 +80,8 @@ const (
|
||||||
const (
|
const (
|
||||||
ClientAuthMethodClientSecretBasic = "client_secret_basic"
|
ClientAuthMethodClientSecretBasic = "client_secret_basic"
|
||||||
ClientAuthMethodClientSecretPost = "client_secret_post"
|
ClientAuthMethodClientSecretPost = "client_secret_post"
|
||||||
|
ClientAuthMethodClientSecretJWT = "client_secret_jwt"
|
||||||
|
ClientAuthMethodPrivateKeyJWT = "private_key_jwt"
|
||||||
ClientAuthMethodNone = "none"
|
ClientAuthMethodNone = "none"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,10 +96,30 @@ const (
|
||||||
ResponseTypeHybridFlowBoth = "code id_token token"
|
ResponseTypeHybridFlowBoth = "code id_token token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Signing Algorithm strings.
|
// JWS Algorithm strings.
|
||||||
|
// See: https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
|
||||||
const (
|
const (
|
||||||
SigningAlgorithmNone = none
|
SigningAlgNone = none
|
||||||
SigningAlgorithmRSAWithSHA256 = "RS256"
|
|
||||||
|
SigningAlgRSAUsingSHA256 = "RS256"
|
||||||
|
SigningAlgRSAUsingSHA384 = "RS384"
|
||||||
|
SigningAlgRSAUsingSHA512 = "RS512"
|
||||||
|
|
||||||
|
SigningAlgRSAPSSUsingSHA256 = "PS256"
|
||||||
|
SigningAlgRSAPSSUsingSHA384 = "PS384"
|
||||||
|
SigningAlgRSAPSSUsingSHA512 = "PS512"
|
||||||
|
|
||||||
|
SigningAlgECDSAUsingP256AndSHA256 = "ES256"
|
||||||
|
SigningAlgECDSAUsingP384AndSHA384 = "ES384"
|
||||||
|
SigningAlgECDSAUsingP521AndSHA512 = "ES512"
|
||||||
|
|
||||||
|
SigningAlgHMACUsingSHA256 = "HS256"
|
||||||
|
SigningAlgHMACUsingSHA384 = "HS384"
|
||||||
|
SigningAlgHMACUsingSHA512 = "HS512"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
KeyUseSignature = "sig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Subject Type strings.
|
// Subject Type strings.
|
||||||
|
@ -108,10 +135,14 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
FormParameterClientID = "client_id"
|
||||||
|
FormParameterClientSecret = "client_secret"
|
||||||
FormParameterRequestURI = "request_uri"
|
FormParameterRequestURI = "request_uri"
|
||||||
FormParameterResponseMode = "response_mode"
|
FormParameterResponseMode = "response_mode"
|
||||||
FormParameterCodeChallenge = "code_challenge"
|
FormParameterCodeChallenge = "code_challenge"
|
||||||
FormParameterCodeChallengeMethod = "code_challenge_method"
|
FormParameterCodeChallengeMethod = "code_challenge_method"
|
||||||
|
FormParameterClientAssertionType = "client_assertion_type"
|
||||||
|
FormParameterClientAssertion = "client_assertion"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -135,6 +166,9 @@ const (
|
||||||
const (
|
const (
|
||||||
// JWTHeaderKeyIdentifier is the JWT Header referencing the JWS Key Identifier used to sign a token.
|
// JWTHeaderKeyIdentifier is the JWT Header referencing the JWS Key Identifier used to sign a token.
|
||||||
JWTHeaderKeyIdentifier = "kid"
|
JWTHeaderKeyIdentifier = "kid"
|
||||||
|
|
||||||
|
// JWTHeaderKeyAlgorithm is the JWT Header referencing the JWS Key algorithm used to sign a token.
|
||||||
|
JWTHeaderKeyAlgorithm = "alg"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
@ -1,12 +1,56 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
const (
|
import (
|
||||||
myclient = "myclient"
|
"crypto/rsa"
|
||||||
myclientdesc = "My Client"
|
"crypto/x509"
|
||||||
onefactor = "one_factor"
|
"encoding/pem"
|
||||||
twofactor = "two_factor"
|
"net/url"
|
||||||
examplecom = "https://example.com"
|
|
||||||
examplecomsid = "example.com"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
badsecret = "$plaintext$a_bad_secret"
|
|
||||||
badhmac = "asbdhaaskmdlkamdklasmdlkams"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
myclient = "myclient"
|
||||||
|
myclientdesc = "My Client"
|
||||||
|
onefactor = "one_factor"
|
||||||
|
twofactor = "two_factor"
|
||||||
|
examplecom = "https://example.com"
|
||||||
|
examplecomsid = "example.com"
|
||||||
|
badsecret = "$plaintext$a_bad_secret"
|
||||||
|
badhmac = "asbdhaaskmdlkamdklasmdlkams"
|
||||||
|
exampleIssuerPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAvcMVMB2vEbqI6PlSNJ4HmUyMxBDJ5iY7FS+zDDAHOZBg9S3S\nKcAn1CZcnyL0VvJ7wcdhR6oTnOwR94eKvzUyJZ+GL2hTMm27dubEYsNdhoCl6N3X\nyEEohNfoxiiCYraVauX8X3M9jFzbEz9+pacaDbHB2syaJ1qFmMNR+HSu2jPzOo7M\nlqKIOgUzA0741MaYNt47AEVg4XU5ORLdolbAkItmYg1QbyFndg9H5IvwKkYaXTGE\nlgDBcPUC0yVjAC15Mguquq+jZeQay+6PSbHTD8PQMOkLjyChI2xEhVNbdCXe676R\ncMW2R/gjrcK23zmtmTWRfdC1iZLSlHO+bJj9vQIDAQABAoIBAEZvkP/JJOCJwqPn\nV3IcbmmilmV4bdi1vByDFgyiDyx4wOSA24+PubjvfFW9XcCgRPuKjDtTj/AhWBHv\nB7stfa2lZuNV7/u562mZArA+IAr62Zp0LdIxDV8x3T8gbjVB3HhPYbv0RJZDKTYd\nzV6jhfIrVu9mHpoY6ZnodhapCPYIyk/d49KBIHZuAc25CUjMXgTeaVtf0c996036\nUxW6ef33wAOJAvW0RCvbXAJfmBeEq2qQlkjTIlpYx71fhZWexHifi8Ouv3Zonc+1\n/P2Adq5uzYVBT92f9RKHg9QxxNzVrLjSMaxyvUtWQCAQfW0tFIRdqBGsHYsQrFtI\nF4yzv8ECgYEA7ntpyN9HD9Z9lYQzPCR73sFCLM+ID99aVij0wHuxK97bkSyyvkLd\n7MyTaym3lg1UEqWNWBCLvFULZx7F0Ah6qCzD4ymm3Bj/ADpWWPgljBI0AFml+HHs\nhcATmXUrj5QbLyhiP2gmJjajp1o/rgATx6ED66seSynD6JOH8wUhhZUCgYEAy7OA\n06PF8GfseNsTqlDjNF0K7lOqd21S0prdwrsJLiVzUlfMM25MLE0XLDUutCnRheeh\nIlcuDoBsVTxz6rkvFGD74N+pgXlN4CicsBq5ofK060PbqCQhSII3fmHobrZ9Cr75\nHmBjAxHx998SKaAAGbBbcYGUAp521i1pH5CEPYkCgYEAkUd1Zf0+2RMdZhwm6hh/\nrW+l1I6IoMK70YkZsLipccRNld7Y9LbfYwYtODcts6di9AkOVfueZJiaXbONZfIE\nZrb+jkAteh9wGL9xIrnohbABJcV3Kiaco84jInUSmGDtPokncOENfHIEuEpuSJ2b\nbx1TuhmAVuGWivR0+ULC7RECgYEAgS0cDRpWc9Xzh9Cl7+PLsXEvdWNpPsL9OsEq\n0Ep7z9+/+f/jZtoTRCS/BTHUpDvAuwHglT5j3p5iFMt5VuiIiovWLwynGYwrbnNS\nqfrIrYKUaH1n1oDS+oBZYLQGCe9/7EifAjxtjYzbvSyg//SPG7tSwfBCREbpZXj2\nqSWkNsECgYA/mCDzCTlrrWPuiepo6kTmN+4TnFA+hJI6NccDVQ+jvbqEdoJ4SW4L\nzqfZSZRFJMNpSgIqkQNRPJqMP0jQ5KRtJrjMWBnYxktwKz9fDg2R2MxdFgMF2LH2\nHEMMhFHlv8NDjVOXh1KwRoltNGVWYsSrD9wKU9GhRCEfmNCGrvBcEg==\n-----END RSA PRIVATE KEY-----"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MustDecodeSecret(value string) *schema.PasswordDigest {
|
||||||
|
if secret, err := schema.DecodePasswordDigest(value); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParseRequestURI(input string) *url.URL {
|
||||||
|
if requestURI, err := url.ParseRequestURI(input); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else {
|
||||||
|
return requestURI
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustParseRSAPrivateKey(data string) *rsa.PrivateKey {
|
||||||
|
block, _ := pem.Decode([]byte(data))
|
||||||
|
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
|
||||||
|
panic("not pem encoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
if block.Type != "RSA PRIVATE KEY" {
|
||||||
|
panic("not private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
|
@ -62,8 +62,14 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
TokenEndpointAuthMethodsSupported: []string{
|
TokenEndpointAuthMethodsSupported: []string{
|
||||||
ClientAuthMethodClientSecretBasic,
|
ClientAuthMethodClientSecretBasic,
|
||||||
ClientAuthMethodClientSecretPost,
|
ClientAuthMethodClientSecretPost,
|
||||||
|
ClientAuthMethodClientSecretJWT,
|
||||||
ClientAuthMethodNone,
|
ClientAuthMethodNone,
|
||||||
},
|
},
|
||||||
|
TokenEndpointAuthSigningAlgValuesSupported: []string{
|
||||||
|
SigningAlgHMACUsingSHA256,
|
||||||
|
SigningAlgHMACUsingSHA384,
|
||||||
|
SigningAlgHMACUsingSHA512,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
|
OAuth2DiscoveryOptions: OAuth2DiscoveryOptions{
|
||||||
CodeChallengeMethodsSupported: []string{
|
CodeChallengeMethodsSupported: []string{
|
||||||
|
@ -77,11 +83,11 @@ func NewOpenIDConnectWellKnownConfiguration(c *schema.OpenIDConnectConfiguration
|
||||||
|
|
||||||
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
|
OpenIDConnectDiscoveryOptions: OpenIDConnectDiscoveryOptions{
|
||||||
IDTokenSigningAlgValuesSupported: []string{
|
IDTokenSigningAlgValuesSupported: []string{
|
||||||
SigningAlgorithmRSAWithSHA256,
|
SigningAlgRSAUsingSHA256,
|
||||||
},
|
},
|
||||||
UserinfoSigningAlgValuesSupported: []string{
|
UserinfoSigningAlgValuesSupported: []string{
|
||||||
SigningAlgorithmNone,
|
SigningAlgNone,
|
||||||
SigningAlgorithmRSAWithSHA256,
|
SigningAlgRSAUsingSHA256,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
|
OpenIDConnectFrontChannelLogoutDiscoveryOptions: &OpenIDConnectFrontChannelLogoutDiscoveryOptions{},
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errPasswordsDoNotMatch = errors.New("the passwords don't match")
|
var errPasswordsDoNotMatch = errors.New("The provided client secret did not match the registered client secret.")
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrIssuerCouldNotDerive is sent when the issuer couldn't be determined from the headers.
|
// ErrIssuerCouldNotDerive is sent when the issuer couldn't be determined from the headers.
|
||||||
|
|
|
@ -125,8 +125,8 @@ func NewJWK(chain schema.X509CertificateChain, key *rsa.PrivateKey) (j *JWK, err
|
||||||
}
|
}
|
||||||
|
|
||||||
jwk := &jose.JSONWebKey{
|
jwk := &jose.JSONWebKey{
|
||||||
Algorithm: SigningAlgorithmRSAWithSHA256,
|
Algorithm: SigningAlgRSAUsingSHA256,
|
||||||
Use: "sig",
|
Use: KeyUseSignature,
|
||||||
Key: &key.PublicKey,
|
Key: &key.PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,8 +170,8 @@ func (j *JWK) JSONWebKey() (jwk *jose.JSONWebKey) {
|
||||||
jwk = &jose.JSONWebKey{
|
jwk = &jose.JSONWebKey{
|
||||||
Key: &j.key.PublicKey,
|
Key: &j.key.PublicKey,
|
||||||
KeyID: j.id,
|
KeyID: j.id,
|
||||||
Algorithm: "RS256",
|
Algorithm: SigningAlgRSAUsingSHA256,
|
||||||
Use: "sig",
|
Use: KeyUseSignature,
|
||||||
Certificates: j.chain.Certificates(),
|
Certificates: j.chain.Certificates(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ func TestKeyManager_AddActiveJWK(t *testing.T) {
|
||||||
assert.Nil(t, manager.jwk)
|
assert.Nil(t, manager.jwk)
|
||||||
assert.Nil(t, manager.Strategy())
|
assert.Nil(t, manager.Strategy())
|
||||||
|
|
||||||
j, err := manager.AddActiveJWK(schema.X509CertificateChain{}, mustParseRSAPrivateKey(exampleIssuerPrivateKey))
|
j, err := manager.AddActiveJWK(schema.X509CertificateChain{}, MustParseRSAPrivateKey(exampleIssuerPrivateKey))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, j)
|
require.NotNil(t, j)
|
||||||
require.NotNil(t, manager.jwk)
|
require.NotNil(t, manager.jwk)
|
||||||
|
|
|
@ -36,6 +36,7 @@ func NewOpenIDConnectProvider(config *schema.OpenIDConnectConfiguration, store s
|
||||||
}
|
}
|
||||||
|
|
||||||
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
|
provider.Config.LoadHandlers(provider.Store, provider.KeyManager.Strategy())
|
||||||
|
provider.Config.Strategy.ClientAuthentication = provider.DefaultClientAuthenticationStrategy
|
||||||
|
|
||||||
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
|
provider.discovery = NewOpenIDConnectWellKnownConfiguration(config)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package oidc
|
package oidc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/pem"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -13,8 +10,6 @@ import (
|
||||||
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
"github.com/authelia/authelia/v4/internal/configuration/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
var exampleIssuerPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAvcMVMB2vEbqI6PlSNJ4HmUyMxBDJ5iY7FS+zDDAHOZBg9S3S\nKcAn1CZcnyL0VvJ7wcdhR6oTnOwR94eKvzUyJZ+GL2hTMm27dubEYsNdhoCl6N3X\nyEEohNfoxiiCYraVauX8X3M9jFzbEz9+pacaDbHB2syaJ1qFmMNR+HSu2jPzOo7M\nlqKIOgUzA0741MaYNt47AEVg4XU5ORLdolbAkItmYg1QbyFndg9H5IvwKkYaXTGE\nlgDBcPUC0yVjAC15Mguquq+jZeQay+6PSbHTD8PQMOkLjyChI2xEhVNbdCXe676R\ncMW2R/gjrcK23zmtmTWRfdC1iZLSlHO+bJj9vQIDAQABAoIBAEZvkP/JJOCJwqPn\nV3IcbmmilmV4bdi1vByDFgyiDyx4wOSA24+PubjvfFW9XcCgRPuKjDtTj/AhWBHv\nB7stfa2lZuNV7/u562mZArA+IAr62Zp0LdIxDV8x3T8gbjVB3HhPYbv0RJZDKTYd\nzV6jhfIrVu9mHpoY6ZnodhapCPYIyk/d49KBIHZuAc25CUjMXgTeaVtf0c996036\nUxW6ef33wAOJAvW0RCvbXAJfmBeEq2qQlkjTIlpYx71fhZWexHifi8Ouv3Zonc+1\n/P2Adq5uzYVBT92f9RKHg9QxxNzVrLjSMaxyvUtWQCAQfW0tFIRdqBGsHYsQrFtI\nF4yzv8ECgYEA7ntpyN9HD9Z9lYQzPCR73sFCLM+ID99aVij0wHuxK97bkSyyvkLd\n7MyTaym3lg1UEqWNWBCLvFULZx7F0Ah6qCzD4ymm3Bj/ADpWWPgljBI0AFml+HHs\nhcATmXUrj5QbLyhiP2gmJjajp1o/rgATx6ED66seSynD6JOH8wUhhZUCgYEAy7OA\n06PF8GfseNsTqlDjNF0K7lOqd21S0prdwrsJLiVzUlfMM25MLE0XLDUutCnRheeh\nIlcuDoBsVTxz6rkvFGD74N+pgXlN4CicsBq5ofK060PbqCQhSII3fmHobrZ9Cr75\nHmBjAxHx998SKaAAGbBbcYGUAp521i1pH5CEPYkCgYEAkUd1Zf0+2RMdZhwm6hh/\nrW+l1I6IoMK70YkZsLipccRNld7Y9LbfYwYtODcts6di9AkOVfueZJiaXbONZfIE\nZrb+jkAteh9wGL9xIrnohbABJcV3Kiaco84jInUSmGDtPokncOENfHIEuEpuSJ2b\nbx1TuhmAVuGWivR0+ULC7RECgYEAgS0cDRpWc9Xzh9Cl7+PLsXEvdWNpPsL9OsEq\n0Ep7z9+/+f/jZtoTRCS/BTHUpDvAuwHglT5j3p5iFMt5VuiIiovWLwynGYwrbnNS\nqfrIrYKUaH1n1oDS+oBZYLQGCe9/7EifAjxtjYzbvSyg//SPG7tSwfBCREbpZXj2\nqSWkNsECgYA/mCDzCTlrrWPuiepo6kTmN+4TnFA+hJI6NccDVQ+jvbqEdoJ4SW4L\nzqfZSZRFJMNpSgIqkQNRPJqMP0jQ5KRtJrjMWBnYxktwKz9fDg2R2MxdFgMF2LH2\nHEMMhFHlv8NDjVOXh1KwRoltNGVWYsSrD9wKU9GhRCEfmNCGrvBcEg==\n-----END RSA PRIVATE KEY-----"
|
|
||||||
|
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(nil, nil, nil)
|
provider, err := NewOpenIDConnectProvider(nil, nil, nil)
|
||||||
|
|
||||||
|
@ -25,7 +20,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_NotConfigured(t *testing
|
||||||
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
|
func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
EnablePKCEPlainChallenge: true,
|
EnablePKCEPlainChallenge: true,
|
||||||
HMACSecret: badhmac,
|
HMACSecret: badhmac,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
|
@ -57,7 +52,7 @@ func TestNewOpenIDConnectProvider_ShouldEnableOptionalDiscoveryValues(t *testing
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
HMACSecret: badhmac,
|
HMACSecret: badhmac,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
|
@ -97,7 +92,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GoodConfiguration(t *tes
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfiguration(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
|
@ -152,9 +147,10 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
|
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
|
||||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
|
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
|
||||||
|
|
||||||
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3)
|
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
||||||
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||||
|
|
||||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
assert.Len(t, disco.GrantTypesSupported, 3)
|
||||||
|
@ -163,11 +159,11 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
|
assert.Contains(t, disco.GrantTypesSupported, GrantTypeImplicit)
|
||||||
|
|
||||||
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
|
assert.Len(t, disco.IDTokenSigningAlgValuesSupported, 1)
|
||||||
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
|
assert.Contains(t, disco.IDTokenSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
|
||||||
|
|
||||||
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
|
assert.Len(t, disco.UserinfoSigningAlgValuesSupported, 2)
|
||||||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmRSAWithSHA256)
|
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgRSAUsingSHA256)
|
||||||
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgorithmNone)
|
assert.Contains(t, disco.UserinfoSigningAlgValuesSupported, SigningAlgNone)
|
||||||
|
|
||||||
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0)
|
assert.Len(t, disco.RequestObjectSigningAlgValuesSupported, 0)
|
||||||
|
|
||||||
|
@ -195,7 +191,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfiguration(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
|
@ -249,9 +245,10 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
||||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
|
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowToken)
|
||||||
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
|
assert.Contains(t, disco.ResponseTypesSupported, ResponseTypeHybridFlowBoth)
|
||||||
|
|
||||||
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 3)
|
assert.Len(t, disco.TokenEndpointAuthMethodsSupported, 4)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretBasic)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretPost)
|
||||||
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodClientSecretJWT)
|
||||||
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
assert.Contains(t, disco.TokenEndpointAuthMethodsSupported, ClientAuthMethodNone)
|
||||||
|
|
||||||
assert.Len(t, disco.GrantTypesSupported, 3)
|
assert.Len(t, disco.GrantTypesSupported, 3)
|
||||||
|
@ -283,7 +280,7 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOAuth2WellKnownConfig
|
||||||
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
|
func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnownConfigurationWithPlainPKCE(t *testing.T) {
|
||||||
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
provider, err := NewOpenIDConnectProvider(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
HMACSecret: "asbdhaaskmdlkamdklasmdlkams",
|
||||||
EnablePKCEPlainChallenge: true,
|
EnablePKCEPlainChallenge: true,
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
|
@ -306,21 +303,3 @@ func TestOpenIDConnectProvider_NewOpenIDConnectProvider_GetOpenIDConnectWellKnow
|
||||||
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
|
assert.Equal(t, PKCEChallengeMethodSHA256, disco.CodeChallengeMethodsSupported[0])
|
||||||
assert.Equal(t, PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1])
|
assert.Equal(t, PKCEChallengeMethodPlain, disco.CodeChallengeMethodsSupported[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustParseRSAPrivateKey(data string) *rsa.PrivateKey {
|
|
||||||
block, _ := pem.Decode([]byte(data))
|
|
||||||
if block == nil || block.Bytes == nil || len(block.Bytes) == 0 {
|
|
||||||
panic("not pem encoded")
|
|
||||||
}
|
|
||||||
|
|
||||||
if block.Type != "RSA PRIVATE KEY" {
|
|
||||||
panic("not private key")
|
|
||||||
}
|
|
||||||
|
|
||||||
key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
|
@ -47,7 +47,7 @@ func TestOpenIDConnectStore_GetClientPolicy(t *testing.T) {
|
||||||
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
func TestOpenIDConnectStore_GetInternalClient(t *testing.T) {
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
|
@ -82,7 +82,7 @@ func TestOpenIDConnectStore_GetInternalClient_ValidClient(t *testing.T) {
|
||||||
|
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
|
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
Clients: []schema.OpenIDConnectClientConfiguration{c1},
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
|
@ -122,7 +122,7 @@ func TestOpenIDConnectStore_GetInternalClient_InvalidClient(t *testing.T) {
|
||||||
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
func TestOpenIDConnectStore_IsValidClientID(t *testing.T) {
|
||||||
s := NewStore(&schema.OpenIDConnectConfiguration{
|
s := NewStore(&schema.OpenIDConnectConfiguration{
|
||||||
IssuerCertificateChain: schema.X509CertificateChain{},
|
IssuerCertificateChain: schema.X509CertificateChain{},
|
||||||
IssuerPrivateKey: mustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
IssuerPrivateKey: MustParseRSAPrivateKey(exampleIssuerPrivateKey),
|
||||||
Clients: []schema.OpenIDConnectClientConfiguration{
|
Clients: []schema.OpenIDConnectClientConfiguration{
|
||||||
{
|
{
|
||||||
ID: myclient,
|
ID: myclient,
|
||||||
|
|
|
@ -958,3 +958,15 @@ type OpenIDConnectContext interface {
|
||||||
|
|
||||||
IssuerURL() (issuerURL *url.URL, err error)
|
IssuerURL() (issuerURL *url.URL, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MockOpenIDConnectContext is a minimal implementation of OpenIDConnectContext for the purpose of testing.
|
||||||
|
type MockOpenIDConnectContext struct {
|
||||||
|
context.Context
|
||||||
|
|
||||||
|
MockIssuerURL *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// IssuerURL returns the MockIssuerURL.
|
||||||
|
func (m *MockOpenIDConnectContext) IssuerURL() (issuerURL *url.URL, err error) {
|
||||||
|
return m.MockIssuerURL, nil
|
||||||
|
}
|
||||||
|
|
|
@ -96,11 +96,3 @@ func TestNewSessionWithAuthorizeRequest(t *testing.T) {
|
||||||
assert.NotNil(t, session.Claims.Extra)
|
assert.NotNil(t, session.Claims.Extra)
|
||||||
assert.Nil(t, session.Claims.AuthenticationMethodsReferences)
|
assert.Nil(t, session.Claims.AuthenticationMethodsReferences)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustParseRequestURI(input string) *url.URL {
|
|
||||||
if requestURI, err := url.ParseRequestURI(input); err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
return requestURI
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue