2021-05-04 22:06:05 +00:00
package validator
import (
"fmt"
"net/url"
2023-04-13 10:58:18 +00:00
"strconv"
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
"strings"
"time"
2021-05-04 22:06:05 +00:00
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/configuration/schema"
2022-10-20 02:16:36 +00:00
"github.com/authelia/authelia/v4/internal/oidc"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/utils"
2021-05-04 22:06:05 +00:00
)
2022-04-17 23:58:24 +00:00
// ValidateIdentityProviders validates and updates the IdentityProviders configuration.
2022-11-23 23:16:23 +00:00
func ValidateIdentityProviders ( config * schema . IdentityProvidersConfiguration , val * schema . StructValidator ) {
validateOIDC ( config . OIDC , val )
2021-05-04 22:06:05 +00:00
}
2022-11-23 23:16:23 +00:00
func validateOIDC ( config * schema . OpenIDConnectConfiguration , val * schema . StructValidator ) {
2022-10-02 02:07:40 +00:00
if config == nil {
return
}
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
2022-10-02 02:07:40 +00:00
setOIDCDefaults ( config )
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
2022-10-20 02:16:36 +00:00
switch {
case config . IssuerPrivateKey == nil :
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCNoPrivateKey ) )
2022-10-20 02:16:36 +00:00
default :
if config . IssuerCertificateChain . HasCertificates ( ) {
if ! config . IssuerCertificateChain . EqualKey ( config . IssuerPrivateKey ) {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCCertificateMismatch ) )
2022-10-20 02:16:36 +00:00
}
if err := config . IssuerCertificateChain . Validate ( ) ; err != nil {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCCertificateChain , err ) )
2022-10-20 02:16:36 +00:00
}
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
2023-04-08 06:02:34 +00:00
if config . IssuerPrivateKey . PublicKey . N == nil {
val . Push ( fmt . Errorf ( errFmtOIDCInvalidPrivateKeyMalformedMissingPublicKey ) )
} else if config . IssuerPrivateKey . Size ( ) * 8 < 2048 {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCInvalidPrivateKeyBitSize , 2048 , config . IssuerPrivateKey . Size ( ) * 8 ) )
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
2022-10-02 02:07:40 +00:00
}
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
2022-10-02 02:07:40 +00:00
if config . MinimumParameterEntropy != 0 && config . MinimumParameterEntropy < 8 {
2022-11-23 23:16:23 +00:00
val . PushWarning ( fmt . Errorf ( errFmtOIDCServerInsecureParameterEntropy , config . MinimumParameterEntropy ) )
2022-10-02 02:07:40 +00:00
}
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
2022-10-02 02:07:40 +00:00
if config . EnforcePKCE != "never" && config . EnforcePKCE != "public_clients_only" && config . EnforcePKCE != "always" {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCEnforcePKCEInvalidValue , config . EnforcePKCE ) )
2022-10-02 02:07:40 +00:00
}
2022-03-02 04:44:05 +00:00
2022-11-23 23:16:23 +00:00
validateOIDCOptionsCORS ( config , val )
2022-03-02 04:44:05 +00:00
2022-10-02 02:07:40 +00:00
if len ( config . Clients ) == 0 {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCNoClientsConfigured ) )
2022-10-02 02:07:40 +00:00
} else {
2022-11-23 23:16:23 +00:00
validateOIDCClients ( config , val )
2022-10-02 02:07:40 +00:00
}
}
2021-05-04 22:06:05 +00:00
2022-10-02 02:07:40 +00:00
func setOIDCDefaults ( config * schema . OpenIDConnectConfiguration ) {
if config . AccessTokenLifespan == time . Duration ( 0 ) {
config . AccessTokenLifespan = schema . DefaultOpenIDConnectConfiguration . AccessTokenLifespan
}
if config . AuthorizeCodeLifespan == time . Duration ( 0 ) {
config . AuthorizeCodeLifespan = schema . DefaultOpenIDConnectConfiguration . AuthorizeCodeLifespan
}
if config . IDTokenLifespan == time . Duration ( 0 ) {
config . IDTokenLifespan = schema . DefaultOpenIDConnectConfiguration . IDTokenLifespan
}
if config . RefreshTokenLifespan == time . Duration ( 0 ) {
config . RefreshTokenLifespan = schema . DefaultOpenIDConnectConfiguration . RefreshTokenLifespan
}
if config . EnforcePKCE == "" {
config . EnforcePKCE = schema . DefaultOpenIDConnectConfiguration . EnforcePKCE
2021-05-04 22:06:05 +00:00
}
}
2022-04-07 00:58:51 +00:00
func validateOIDCOptionsCORS ( config * schema . OpenIDConnectConfiguration , validator * schema . StructValidator ) {
validateOIDCOptionsCORSAllowedOrigins ( config , validator )
if config . CORS . AllowedOriginsFromClientRedirectURIs {
validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs ( config )
}
validateOIDCOptionsCORSEndpoints ( config , validator )
}
2022-11-23 23:16:23 +00:00
func validateOIDCOptionsCORSAllowedOrigins ( config * schema . OpenIDConnectConfiguration , val * schema . StructValidator ) {
2022-04-07 00:58:51 +00:00
for _ , origin := range config . CORS . AllowedOrigins {
if origin . String ( ) == "*" {
if len ( config . CORS . AllowedOrigins ) != 1 {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCCORSInvalidOriginWildcard ) )
2022-04-07 00:58:51 +00:00
}
if config . CORS . AllowedOriginsFromClientRedirectURIs {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCCORSInvalidOriginWildcardWithClients ) )
2022-04-07 00:58:51 +00:00
}
continue
}
if origin . Path != "" {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCCORSInvalidOrigin , origin . String ( ) , "path" ) )
2022-04-07 00:58:51 +00:00
}
if origin . RawQuery != "" {
2022-11-23 23:16:23 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCCORSInvalidOrigin , origin . String ( ) , "query string" ) )
2022-04-07 00:58:51 +00:00
}
}
}
func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs ( config * schema . OpenIDConnectConfiguration ) {
for _ , client := range config . Clients {
for _ , redirectURI := range client . RedirectURIs {
2022-09-03 01:51:02 +00:00
uri , err := url . ParseRequestURI ( redirectURI )
2022-04-07 00:58:51 +00:00
if err != nil || ( uri . Scheme != schemeHTTP && uri . Scheme != schemeHTTPS ) || uri . Hostname ( ) == "localhost" {
continue
}
2023-04-13 10:58:18 +00:00
origin := utils . OriginFromURL ( uri )
2022-04-07 00:58:51 +00:00
2023-04-13 10:58:18 +00:00
if ! utils . IsURLInSlice ( * origin , config . CORS . AllowedOrigins ) {
config . CORS . AllowedOrigins = append ( config . CORS . AllowedOrigins , * origin )
2022-04-07 00:58:51 +00:00
}
}
}
}
2022-11-23 23:16:23 +00:00
func validateOIDCOptionsCORSEndpoints ( config * schema . OpenIDConnectConfiguration , val * schema . StructValidator ) {
2022-04-07 00:58:51 +00:00
for _ , endpoint := range config . CORS . Endpoints {
if ! utils . IsStringInSlice ( endpoint , validOIDCCORSEndpoints ) {
2023-04-13 10:58:18 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCCORSInvalidEndpoint , endpoint , strJoinOr ( validOIDCCORSEndpoints ) ) )
2022-04-07 00:58:51 +00:00
}
}
}
2022-09-03 01:51:02 +00:00
2022-11-23 23:16:23 +00:00
func validateOIDCClients ( config * schema . OpenIDConnectConfiguration , val * schema . StructValidator ) {
2023-04-13 10:58:18 +00:00
var (
errDeprecated bool
2021-05-04 22:06:05 +00:00
2023-04-13 10:58:18 +00:00
clientIDs , duplicateClientIDs , blankClientIDs [ ] string
)
errDeprecatedFunc := func ( ) { errDeprecated = true }
2021-05-04 22:06:05 +00:00
2022-02-28 03:15:01 +00:00
for c , client := range config . Clients {
2021-05-04 22:06:05 +00:00
if client . ID == "" {
2023-04-13 10:58:18 +00:00
blankClientIDs = append ( blankClientIDs , "#" + strconv . Itoa ( c + 1 ) )
2021-05-04 22:06:05 +00:00
} else {
if client . Description == "" {
2022-02-28 03:15:01 +00:00
config . Clients [ c ] . Description = client . ID
2021-05-04 22:06:05 +00:00
}
2023-04-13 10:58:18 +00:00
if id := strings . ToLower ( client . ID ) ; utils . IsStringInSlice ( id , clientIDs ) {
if ! utils . IsStringInSlice ( id , duplicateClientIDs ) {
duplicateClientIDs = append ( duplicateClientIDs , id )
}
} else {
clientIDs = append ( clientIDs , id )
2021-05-04 22:06:05 +00:00
}
}
2023-04-13 10:58:18 +00:00
validateOIDCClient ( c , config , val , errDeprecatedFunc )
}
2021-05-04 22:06:05 +00:00
2023-04-13 10:58:18 +00:00
if errDeprecated {
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientsDeprecated ) )
}
2021-05-04 22:06:05 +00:00
2023-04-13 10:58:18 +00:00
if len ( blankClientIDs ) != 0 {
val . Push ( fmt . Errorf ( errFmtOIDCClientsWithEmptyID , buildJoinedString ( ", " , "or" , "" , blankClientIDs ) ) )
}
2023-01-03 15:03:23 +00:00
2023-04-13 10:58:18 +00:00
if len ( duplicateClientIDs ) != 0 {
val . Push ( fmt . Errorf ( errFmtOIDCClientsDuplicateID , strJoinOr ( duplicateClientIDs ) ) )
2021-05-04 22:06:05 +00:00
}
2023-04-13 10:58:18 +00:00
}
2021-05-04 22:06:05 +00:00
2023-04-13 10:58:18 +00:00
func validateOIDCClient ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
if config . Clients [ c ] . Public {
if config . Clients [ c ] . Secret != nil {
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicInvalidSecret , config . Clients [ c ] . ID ) )
}
} else {
if config . Clients [ c ] . Secret == nil {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidSecret , config . Clients [ c ] . ID ) )
2023-04-15 10:55:38 +00:00
} else {
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 ) )
}
2023-04-13 10:58:18 +00:00
}
2021-05-04 22:06:05 +00:00
}
2023-04-13 10:58:18 +00:00
switch config . Clients [ c ] . Policy {
case "" :
config . Clients [ c ] . Policy = schema . DefaultOpenIDConnectClientConfiguration . Policy
case policyOneFactor , policyTwoFactor :
break
default :
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue , config . Clients [ c ] . ID , "policy" , strJoinOr ( [ ] string { policyOneFactor , policyTwoFactor } ) , config . Clients [ c ] . Policy ) )
}
switch config . Clients [ c ] . PKCEChallengeMethod {
case "" , oidc . PKCEChallengeMethodPlain , oidc . PKCEChallengeMethodSHA256 :
break
default :
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue , config . Clients [ c ] . ID , attrOIDCPKCEChallengeMethod , strJoinOr ( [ ] string { oidc . PKCEChallengeMethodPlain , oidc . PKCEChallengeMethodSHA256 } ) , config . Clients [ c ] . PKCEChallengeMethod ) )
2021-05-04 22:06:05 +00:00
}
2023-04-13 10:58:18 +00:00
validateOIDCClientConsentMode ( c , config , val )
validateOIDCClientScopes ( c , config , val , errDeprecatedFunc )
validateOIDCClientResponseTypes ( c , config , val , errDeprecatedFunc )
validateOIDCClientResponseModes ( c , config , val , errDeprecatedFunc )
validateOIDCClientGrantTypes ( c , config , val , errDeprecatedFunc )
validateOIDCClientRedirectURIs ( c , config , val , errDeprecatedFunc )
2023-04-15 10:55:38 +00:00
validateOIDCClientTokenEndpointAuth ( c , config , val )
2023-04-13 10:58:18 +00:00
validateOIDDClientUserinfoAlgorithm ( c , config , val )
validateOIDCClientSectorIdentifier ( c , config , val )
2021-05-04 22:06:05 +00:00
}
2023-04-13 10:58:18 +00:00
func validateOIDCClientSectorIdentifier ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator ) {
if config . Clients [ c ] . SectorIdentifier . String ( ) != "" {
if utils . IsURLHostComponent ( config . Clients [ c ] . SectorIdentifier ) || utils . IsURLHostComponentWithPort ( config . Clients [ c ] . SectorIdentifier ) {
2022-04-08 07:38:38 +00:00
return
}
2023-04-13 10:58:18 +00:00
if config . Clients [ c ] . SectorIdentifier . Scheme != "" {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidSectorIdentifier , config . Clients [ c ] . ID , config . Clients [ c ] . SectorIdentifier . String ( ) , config . Clients [ c ] . SectorIdentifier . Host , "scheme" , config . Clients [ c ] . SectorIdentifier . Scheme ) )
2022-04-07 06:13:01 +00:00
2023-04-13 10:58:18 +00:00
if config . Clients [ c ] . SectorIdentifier . Path != "" {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidSectorIdentifier , config . Clients [ c ] . ID , config . Clients [ c ] . SectorIdentifier . String ( ) , config . Clients [ c ] . SectorIdentifier . Host , "path" , config . Clients [ c ] . SectorIdentifier . Path ) )
2022-04-07 06:13:01 +00:00
}
2023-04-13 10:58:18 +00:00
if config . Clients [ c ] . SectorIdentifier . RawQuery != "" {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidSectorIdentifier , config . Clients [ c ] . ID , config . Clients [ c ] . SectorIdentifier . String ( ) , config . Clients [ c ] . SectorIdentifier . Host , "query" , config . Clients [ c ] . SectorIdentifier . RawQuery ) )
2022-04-07 06:13:01 +00:00
}
2023-04-13 10:58:18 +00:00
if config . Clients [ c ] . SectorIdentifier . Fragment != "" {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidSectorIdentifier , config . Clients [ c ] . ID , config . Clients [ c ] . SectorIdentifier . String ( ) , config . Clients [ c ] . SectorIdentifier . Host , "fragment" , config . Clients [ c ] . SectorIdentifier . Fragment ) )
2022-04-07 06:13:01 +00:00
}
2023-04-13 10:58:18 +00:00
if config . Clients [ c ] . SectorIdentifier . User != nil {
if config . Clients [ c ] . SectorIdentifier . User . Username ( ) != "" {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidSectorIdentifier , config . Clients [ c ] . ID , config . Clients [ c ] . SectorIdentifier . String ( ) , config . Clients [ c ] . SectorIdentifier . Host , "username" , config . Clients [ c ] . SectorIdentifier . User . Username ( ) ) )
2022-04-07 06:13:01 +00:00
}
2023-04-13 10:58:18 +00:00
if _ , set := config . Clients [ c ] . SectorIdentifier . User . Password ( ) ; set {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidSectorIdentifierWithoutValue , config . Clients [ c ] . ID , config . Clients [ c ] . SectorIdentifier . String ( ) , config . Clients [ c ] . SectorIdentifier . Host , "password" ) )
2022-04-07 06:13:01 +00:00
}
}
2023-04-13 10:58:18 +00:00
} else if config . Clients [ c ] . SectorIdentifier . Host == "" {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidSectorIdentifierHost , config . Clients [ c ] . ID , config . Clients [ c ] . SectorIdentifier . String ( ) ) )
2022-11-23 23:16:23 +00:00
}
}
}
func validateOIDCClientConsentMode ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator ) {
switch {
2023-04-13 10:58:18 +00:00
case utils . IsStringInSlice ( config . Clients [ c ] . ConsentMode , [ ] string { "" , auto } ) :
2022-11-23 23:16:23 +00:00
if config . Clients [ c ] . ConsentPreConfiguredDuration != nil {
config . Clients [ c ] . ConsentMode = oidc . ClientConsentModePreConfigured . String ( )
} else {
config . Clients [ c ] . ConsentMode = oidc . ClientConsentModeExplicit . String ( )
2022-04-07 06:13:01 +00:00
}
2022-11-23 23:16:23 +00:00
case utils . IsStringInSlice ( config . Clients [ c ] . ConsentMode , validOIDCClientConsentModes ) :
break
default :
2023-04-13 10:58:18 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidConsentMode , config . Clients [ c ] . ID , strJoinOr ( append ( validOIDCClientConsentModes , auto ) ) , config . Clients [ c ] . ConsentMode ) )
2022-11-23 23:16:23 +00:00
}
if config . Clients [ c ] . ConsentMode == oidc . ClientConsentModePreConfigured . String ( ) && config . Clients [ c ] . ConsentPreConfiguredDuration == nil {
config . Clients [ c ] . ConsentPreConfiguredDuration = schema . DefaultOpenIDConnectClientConfiguration . ConsentPreConfiguredDuration
2022-04-07 06:13:01 +00:00
}
}
2023-04-13 10:58:18 +00:00
func validateOIDCClientScopes ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
2022-11-23 23:16:23 +00:00
if len ( config . Clients [ c ] . Scopes ) == 0 {
config . Clients [ c ] . Scopes = schema . DefaultOpenIDConnectClientConfiguration . Scopes
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
2022-11-23 23:16:23 +00:00
if ! utils . IsStringInSlice ( oidc . ScopeOpenID , config . Clients [ c ] . Scopes ) {
2023-04-13 10:58:18 +00:00
config . Clients [ c ] . Scopes = append ( [ ] string { oidc . ScopeOpenID } , config . Clients [ c ] . Scopes ... )
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
2023-04-13 10:58:18 +00:00
invalid , duplicates := validateList ( config . Clients [ c ] . Scopes , validOIDCClientScopes , true )
if len ( invalid ) != 0 {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidEntries , config . Clients [ c ] . ID , attrOIDCScopes , strJoinOr ( validOIDCClientScopes ) , strJoinAnd ( invalid ) ) )
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
2023-04-13 10:58:18 +00:00
if len ( duplicates ) != 0 {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidEntryDuplicates , config . Clients [ c ] . ID , attrOIDCScopes , strJoinAnd ( duplicates ) ) )
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
2023-04-13 10:58:18 +00:00
if utils . IsStringSliceContainsAny ( [ ] string { oidc . ScopeOfflineAccess , oidc . ScopeOffline } , config . Clients [ c ] . Scopes ) &&
! utils . IsStringSliceContainsAny ( validOIDCClientResponseTypesRefreshToken , config . Clients [ c ] . ResponseTypes ) {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType ,
config . Clients [ c ] . ID , attrOIDCScopes ,
strJoinOr ( [ ] string { oidc . ScopeOfflineAccess , oidc . ScopeOffline } ) ,
strJoinOr ( validOIDCClientResponseTypesRefreshToken ) ) ,
)
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
}
2023-04-13 10:58:18 +00:00
func validateOIDCClientResponseTypes ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
2022-11-23 23:16:23 +00:00
if len ( config . Clients [ c ] . ResponseTypes ) == 0 {
config . Clients [ c ] . ResponseTypes = schema . DefaultOpenIDConnectClientConfiguration . ResponseTypes
2023-04-13 10:58:18 +00:00
}
invalid , duplicates := validateList ( config . Clients [ c ] . ResponseTypes , validOIDCClientResponseTypes , true )
if len ( invalid ) != 0 {
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidEntries , config . Clients [ c ] . ID , attrOIDCResponseTypes , strJoinOr ( validOIDCClientResponseTypes ) , strJoinAnd ( invalid ) ) )
}
if len ( duplicates ) != 0 {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidEntryDuplicates , config . Clients [ c ] . ID , attrOIDCResponseTypes , strJoinAnd ( duplicates ) ) )
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
}
2023-04-13 10:58:18 +00:00
func validateOIDCClientResponseModes ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
2022-11-23 23:16:23 +00:00
if len ( config . Clients [ c ] . ResponseModes ) == 0 {
config . Clients [ c ] . ResponseModes = schema . DefaultOpenIDConnectClientConfiguration . ResponseModes
2023-04-13 10:58:18 +00:00
for _ , responseType := range config . Clients [ c ] . ResponseTypes {
switch responseType {
case oidc . ResponseTypeAuthorizationCodeFlow :
if ! utils . IsStringInSlice ( oidc . ResponseModeQuery , config . Clients [ c ] . ResponseModes ) {
config . Clients [ c ] . ResponseModes = append ( config . Clients [ c ] . ResponseModes , oidc . ResponseModeQuery )
}
case oidc . ResponseTypeImplicitFlowIDToken , oidc . ResponseTypeImplicitFlowToken , oidc . ResponseTypeImplicitFlowBoth ,
oidc . ResponseTypeHybridFlowIDToken , oidc . ResponseTypeHybridFlowToken , oidc . ResponseTypeHybridFlowBoth :
if ! utils . IsStringInSlice ( oidc . ResponseModeFragment , config . Clients [ c ] . ResponseModes ) {
config . Clients [ c ] . ResponseModes = append ( config . Clients [ c ] . ResponseModes , oidc . ResponseModeFragment )
}
}
}
}
invalid , duplicates := validateList ( config . Clients [ c ] . ResponseModes , validOIDCClientResponseModes , true )
if len ( invalid ) != 0 {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidEntries , config . Clients [ c ] . ID , attrOIDCResponseModes , strJoinOr ( validOIDCClientResponseModes ) , strJoinAnd ( invalid ) ) )
}
if len ( duplicates ) != 0 {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidEntryDuplicates , config . Clients [ c ] . ID , attrOIDCResponseModes , strJoinAnd ( duplicates ) ) )
}
}
func validateOIDCClientGrantTypes ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
if len ( config . Clients [ c ] . GrantTypes ) == 0 {
validateOIDCClientGrantTypesSetDefaults ( c , config )
}
validateOIDCClientGrantTypesCheckRelated ( c , config , val , errDeprecatedFunc )
invalid , duplicates := validateList ( config . Clients [ c ] . GrantTypes , validOIDCClientGrantTypes , true )
if len ( invalid ) != 0 {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidEntries , config . Clients [ c ] . ID , attrOIDCGrantTypes , strJoinOr ( validOIDCClientGrantTypes ) , strJoinAnd ( invalid ) ) )
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
2023-04-13 10:58:18 +00:00
if len ( duplicates ) != 0 {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidEntryDuplicates , config . Clients [ c ] . ID , attrOIDCGrantTypes , strJoinAnd ( duplicates ) ) )
}
}
func validateOIDCClientGrantTypesSetDefaults ( c int , config * schema . OpenIDConnectConfiguration ) {
for _ , responseType := range config . Clients [ c ] . ResponseTypes {
switch responseType {
case oidc . ResponseTypeAuthorizationCodeFlow :
if ! utils . IsStringInSlice ( oidc . GrantTypeAuthorizationCode , config . Clients [ c ] . GrantTypes ) {
config . Clients [ c ] . GrantTypes = append ( config . Clients [ c ] . GrantTypes , oidc . GrantTypeAuthorizationCode )
}
case oidc . ResponseTypeImplicitFlowIDToken , oidc . ResponseTypeImplicitFlowToken , oidc . ResponseTypeImplicitFlowBoth :
if ! utils . IsStringInSlice ( oidc . GrantTypeImplicit , config . Clients [ c ] . GrantTypes ) {
config . Clients [ c ] . GrantTypes = append ( config . Clients [ c ] . GrantTypes , oidc . GrantTypeImplicit )
}
case oidc . ResponseTypeHybridFlowIDToken , oidc . ResponseTypeHybridFlowToken , oidc . ResponseTypeHybridFlowBoth :
if ! utils . IsStringInSlice ( oidc . GrantTypeAuthorizationCode , config . Clients [ c ] . GrantTypes ) {
config . Clients [ c ] . GrantTypes = append ( config . Clients [ c ] . GrantTypes , oidc . GrantTypeAuthorizationCode )
}
if ! utils . IsStringInSlice ( oidc . GrantTypeImplicit , config . Clients [ c ] . GrantTypes ) {
config . Clients [ c ] . GrantTypes = append ( config . Clients [ c ] . GrantTypes , oidc . GrantTypeImplicit )
}
feat(oidc): add additional config options, accurate token times, and refactoring (#1991)
* This gives admins more control over their OIDC installation exposing options that had defaults before. Things like lifespans for authorize codes, access tokens, id tokens, refresh tokens, a option to enable the debug client messages, minimum parameter entropy. It also allows admins to configure the response modes.
* Additionally this records specific values about a users session indicating when they performed a specific authz factor so this is represented in the token accurately.
* Lastly we also implemented a OIDC key manager which calculates the kid for jwk's using the SHA1 digest instead of being static, or more specifically the first 7 chars. As per https://datatracker.ietf.org/doc/html/draft-ietf-jose-json-web-key#section-8.1.1 the kid should not exceed 8 chars. While it's allowed to exceed 8 chars, it must only be done so with a compelling reason, which we do not have.
2021-07-03 23:44:30 +00:00
}
}
}
2023-04-13 10:58:18 +00:00
func validateOIDCClientGrantTypesCheckRelated ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
for _ , grantType := range config . Clients [ c ] . GrantTypes {
switch grantType {
case oidc . GrantTypeImplicit :
if ! utils . IsStringSliceContainsAny ( validOIDCClientResponseTypesImplicitFlow , config . Clients [ c ] . ResponseTypes ) && ! utils . IsStringSliceContainsAny ( validOIDCClientResponseTypesHybridFlow , config . Clients [ c ] . ResponseTypes ) {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidGrantTypeMatch , config . Clients [ c ] . ID , grantType , "for either the implicit or hybrid flow" , strJoinOr ( append ( append ( [ ] string { } , validOIDCClientResponseTypesImplicitFlow ... ) , validOIDCClientResponseTypesHybridFlow ... ) ) , strJoinAnd ( config . Clients [ c ] . ResponseTypes ) ) )
}
case oidc . GrantTypeAuthorizationCode :
if ! utils . IsStringInSlice ( oidc . ResponseTypeAuthorizationCodeFlow , config . Clients [ c ] . ResponseTypes ) && ! utils . IsStringSliceContainsAny ( validOIDCClientResponseTypesHybridFlow , config . Clients [ c ] . ResponseTypes ) {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidGrantTypeMatch , config . Clients [ c ] . ID , grantType , "for either the authorization code or hybrid flow" , strJoinOr ( append ( [ ] string { oidc . ResponseTypeAuthorizationCodeFlow } , validOIDCClientResponseTypesHybridFlow ... ) ) , strJoinAnd ( config . Clients [ c ] . ResponseTypes ) ) )
}
case oidc . GrantTypeRefreshToken :
if ! utils . IsStringSliceContainsAny ( [ ] string { oidc . ScopeOfflineAccess , oidc . ScopeOffline } , config . Clients [ c ] . Scopes ) {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidGrantTypeRefresh , config . Clients [ c ] . ID ) )
}
if ! utils . IsStringSliceContainsAny ( validOIDCClientResponseTypesRefreshToken , config . Clients [ c ] . ResponseTypes ) {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidRefreshTokenOptionWithoutCodeResponseType ,
config . Clients [ c ] . ID , attrOIDCGrantTypes ,
strJoinOr ( [ ] string { oidc . GrantTypeRefreshToken } ) ,
strJoinOr ( validOIDCClientResponseTypesRefreshToken ) ) ,
)
}
}
2021-07-10 04:56:33 +00:00
}
}
2023-04-13 10:58:18 +00:00
func validateOIDCClientRedirectURIs ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
var (
parsedRedirectURI * url . URL
err error
)
for _ , redirectURI := range config . Clients [ c ] . RedirectURIs {
2021-07-15 11:02:03 +00:00
if redirectURI == oauth2InstalledApp {
2023-04-13 10:58:18 +00:00
if config . Clients [ c ] . Public {
2021-07-15 11:02:03 +00:00
continue
}
2023-04-13 10:58:18 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCClientRedirectURIPublic , config . Clients [ c ] . ID , oauth2InstalledApp ) )
2021-05-04 22:06:05 +00:00
2021-07-15 11:02:03 +00:00
continue
}
2023-04-13 10:58:18 +00:00
if parsedRedirectURI , err = url . Parse ( redirectURI ) ; err != nil {
val . Push ( fmt . Errorf ( errFmtOIDCClientRedirectURICantBeParsed , config . Clients [ c ] . ID , redirectURI , err ) )
2021-07-15 11:02:03 +00:00
continue
}
2023-04-13 10:58:18 +00:00
if ! parsedRedirectURI . IsAbs ( ) || ( ! config . Clients [ c ] . Public && parsedRedirectURI . Scheme == "" ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientRedirectURIAbsolute , config . Clients [ c ] . ID , redirectURI ) )
2021-07-15 11:02:03 +00:00
return
2021-05-04 22:06:05 +00:00
}
}
2023-04-13 10:58:18 +00:00
_ , duplicates := validateList ( config . Clients [ c ] . RedirectURIs , nil , true )
if len ( duplicates ) != 0 {
errDeprecatedFunc ( )
val . PushWarning ( fmt . Errorf ( errFmtOIDCClientInvalidEntryDuplicates , config . Clients [ c ] . ID , attrOIDCRedirectURIs , strJoinAnd ( duplicates ) ) )
}
}
2023-04-15 10:55:38 +00:00
func validateOIDCClientTokenEndpointAuth ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator ) {
2023-04-13 10:58:18 +00:00
implcit := len ( config . Clients [ c ] . ResponseTypes ) != 0 && utils . IsStringSliceContainsAll ( config . Clients [ c ] . ResponseTypes , validOIDCClientResponseTypesImplicitFlow )
switch {
case config . Clients [ c ] . TokenEndpointAuthMethod == "" :
break
case ! utils . IsStringInSlice ( config . Clients [ c ] . TokenEndpointAuthMethod , validOIDCClientTokenEndpointAuthMethods ) :
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue ,
config . Clients [ c ] . ID , attrOIDCTokenAuthMethod , strJoinOr ( validOIDCClientTokenEndpointAuthMethods ) , config . Clients [ c ] . TokenEndpointAuthMethod ) )
case config . Clients [ c ] . TokenEndpointAuthMethod == oidc . ClientAuthMethodNone && ! config . Clients [ c ] . Public && ! implcit :
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidTokenEndpointAuthMethod ,
config . Clients [ c ] . ID , strJoinOr ( validOIDCClientTokenEndpointAuthMethodsConfidential ) , strJoinAnd ( validOIDCClientResponseTypesImplicitFlow ) , config . Clients [ c ] . TokenEndpointAuthMethod ) )
case config . Clients [ c ] . TokenEndpointAuthMethod != oidc . ClientAuthMethodNone && config . Clients [ c ] . Public :
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidTokenEndpointAuthMethodPublic ,
config . Clients [ c ] . ID , config . Clients [ c ] . TokenEndpointAuthMethod ) )
}
2023-04-15 10:55:38 +00:00
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 ) )
}
}
2023-04-13 10:58:18 +00:00
}
func validateOIDDClientUserinfoAlgorithm ( c int , config * schema . OpenIDConnectConfiguration , val * schema . StructValidator ) {
if config . Clients [ c ] . UserinfoSigningAlgorithm == "" {
config . Clients [ c ] . UserinfoSigningAlgorithm = schema . DefaultOpenIDConnectClientConfiguration . UserinfoSigningAlgorithm
}
if ! utils . IsStringInSlice ( config . Clients [ c ] . UserinfoSigningAlgorithm , validOIDCClientUserinfoAlgorithms ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue ,
config . Clients [ c ] . ID , attrOIDCUsrSigAlg , strJoinOr ( validOIDCClientUserinfoAlgorithms ) , config . Clients [ c ] . UserinfoSigningAlgorithm ) )
}
2021-05-04 22:06:05 +00:00
}