2021-05-04 22:06:05 +00:00
package validator
import (
2023-05-15 00:03:19 +00:00
"crypto/ecdsa"
"crypto/rsa"
2021-05-04 22:06:05 +00:00
"fmt"
"net/url"
2023-05-15 00:03:19 +00:00
"sort"
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
2023-05-28 01:50:55 +00:00
"github.com/ory/fosite"
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.
2023-05-22 11:14:32 +00:00
func ValidateIdentityProviders ( config * schema . IdentityProviders , val * schema . StructValidator ) {
2022-11-23 23:16:23 +00:00
validateOIDC ( config . OIDC , val )
2021-05-04 22:06:05 +00:00
}
2023-05-22 11:14:32 +00:00
func validateOIDC ( config * schema . OpenIDConnect , 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
2023-05-15 00:03:19 +00:00
validateOIDCIssuer ( config , val )
2023-05-25 13:28:21 +00:00
validateOIDCPolicies ( config , val )
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-05-15 00:32:10 +00:00
sort . Sort ( oidc . SortedSigningAlgs ( config . Discovery . ResponseObjectSigningAlgs ) )
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-05-28 01:50:55 +00:00
switch {
case config . MinimumParameterEntropy == - 1 :
val . PushWarning ( fmt . Errorf ( errFmtOIDCProviderInsecureDisabledParameterEntropy ) )
case config . MinimumParameterEntropy <= 0 :
config . MinimumParameterEntropy = fosite . MinParameterEntropy
case config . MinimumParameterEntropy < fosite . MinParameterEntropy :
val . PushWarning ( fmt . Errorf ( errFmtOIDCProviderInsecureParameterEntropy , fosite . MinParameterEntropy , 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
2023-05-15 00:32:10 +00:00
switch config . EnforcePKCE {
case "always" , "never" , "public_clients_only" :
break
default :
val . Push ( fmt . Errorf ( errFmtOIDCProviderEnforcePKCEInvalidValue , 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 {
2023-05-15 00:32:10 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCProviderNoClientsConfigured ) )
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
2023-05-25 13:28:21 +00:00
func validateOIDCPolicies ( config * schema . OpenIDConnect , val * schema . StructValidator ) {
config . Discovery . Policies = [ ] string { policyOneFactor , policyTwoFactor }
for name , policy := range config . Policies {
switch name {
case "" :
val . Push ( fmt . Errorf ( "policy must have a name" ) )
case policyOneFactor , policyTwoFactor :
val . Push ( fmt . Errorf ( "policy names can't be named one_factor or two_factor" ) )
default :
break
}
switch policy . DefaultPolicy {
case "" :
policy . DefaultPolicy = policyTwoFactor
case policyOneFactor , policyTwoFactor :
break
default :
val . Push ( fmt . Errorf ( "policy must be one of one_factor or two_factor" ) )
}
if len ( policy . Rules ) == 0 {
val . Push ( fmt . Errorf ( "policy must include at least one rule" ) )
}
for i , rule := range policy . Rules {
switch rule . Policy {
case "" :
policy . Rules [ i ] . Policy = policyTwoFactor
case policyOneFactor , policyTwoFactor :
break
default :
val . Push ( fmt . Errorf ( "policy must be one of one_factor or two_factor" ) )
}
if len ( rule . Subjects ) == 0 {
val . Push ( fmt . Errorf ( "policy must include at least one criteria" ) )
}
}
config . Policies [ name ] = policy
config . Discovery . Policies = append ( config . Discovery . Policies , name )
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCIssuer ( config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-05-15 00:03:19 +00:00
switch {
case config . IssuerPrivateKey != nil :
2023-05-15 00:32:10 +00:00
validateOIDCIssuerPrivateKey ( config )
2023-05-15 00:03:19 +00:00
fallthrough
2023-05-15 00:32:10 +00:00
case len ( config . IssuerPrivateKeys ) != 0 :
validateOIDCIssuerPrivateKeys ( config , val )
2023-05-15 00:03:19 +00:00
default :
2023-05-15 00:32:10 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCProviderNoPrivateKey ) )
2023-05-15 00:03:19 +00:00
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCIssuerPrivateKey ( config * schema . OpenIDConnect ) {
2023-05-15 00:32:10 +00:00
config . IssuerPrivateKeys = append ( [ ] schema . JWK { {
2023-05-15 00:03:19 +00:00
Algorithm : oidc . SigningAlgRSAUsingSHA256 ,
Use : oidc . KeyUseSignature ,
Key : config . IssuerPrivateKey ,
CertificateChain : config . IssuerCertificateChain ,
2023-05-15 00:32:10 +00:00
} } , config . IssuerPrivateKeys ... )
2023-05-15 00:03:19 +00:00
}
2023-05-22 11:14:32 +00:00
func validateOIDCIssuerPrivateKeys ( config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-05-15 00:03:19 +00:00
var (
props * JWKProperties
err error
)
2023-05-22 11:14:32 +00:00
config . Discovery . ResponseObjectSigningKeyIDs = make ( [ ] string , len ( config . IssuerPrivateKeys ) )
config . Discovery . DefaultKeyIDs = map [ string ] string { }
2023-05-15 00:03:19 +00:00
2023-05-15 00:32:10 +00:00
for i := 0 ; i < len ( config . IssuerPrivateKeys ) ; i ++ {
if key , ok := config . IssuerPrivateKeys [ i ] . Key . ( * rsa . PrivateKey ) ; ok && key . PublicKey . N == nil {
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysInvalid , i + 1 ) )
2023-05-15 00:03:19 +00:00
continue
}
2023-05-15 00:32:10 +00:00
switch n := len ( config . IssuerPrivateKeys [ i ] . KeyID ) ; {
2023-05-15 00:03:19 +00:00
case n == 0 :
2023-05-15 00:32:10 +00:00
if config . IssuerPrivateKeys [ i ] . KeyID , err = jwkCalculateThumbprint ( config . IssuerPrivateKeys [ i ] . Key ) ; err != nil {
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysCalcThumbprint , i + 1 , err ) )
2023-05-15 00:03:19 +00:00
continue
}
2023-05-22 11:14:32 +00:00
case n > 100 :
2023-05-15 00:32:10 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysKeyIDLength , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID ) )
2023-05-15 00:03:19 +00:00
}
2023-05-22 11:14:32 +00:00
if config . IssuerPrivateKeys [ i ] . KeyID != "" && utils . IsStringInSlice ( config . IssuerPrivateKeys [ i ] . KeyID , config . Discovery . ResponseObjectSigningKeyIDs ) {
2023-05-15 00:32:10 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysAttributeNotUnique , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID , attrOIDCKeyID ) )
2023-05-15 00:03:19 +00:00
}
2023-05-22 11:14:32 +00:00
config . Discovery . ResponseObjectSigningKeyIDs [ i ] = config . IssuerPrivateKeys [ i ] . KeyID
2023-05-15 00:03:19 +00:00
2023-05-22 11:14:32 +00:00
if ! reOpenIDConnectKID . MatchString ( config . IssuerPrivateKeys [ i ] . KeyID ) {
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysKeyIDNotValid , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID ) )
2023-05-15 00:03:19 +00:00
}
2023-05-15 00:32:10 +00:00
if props , err = schemaJWKGetProperties ( config . IssuerPrivateKeys [ i ] ) ; err != nil {
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysProperties , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID , err ) )
2023-05-15 00:03:19 +00:00
continue
}
2023-05-15 00:32:10 +00:00
validateOIDCIssuerPrivateKeysUseAlg ( i , props , config , val )
validateOIDCIssuerPrivateKeyPair ( i , config , val )
}
2023-05-15 00:03:19 +00:00
2023-05-15 00:32:10 +00:00
if len ( config . Discovery . ResponseObjectSigningAlgs ) != 0 && ! utils . IsStringInSlice ( oidc . SigningAlgRSAUsingSHA256 , config . Discovery . ResponseObjectSigningAlgs ) {
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysNoRS256 , oidc . SigningAlgRSAUsingSHA256 , strJoinAnd ( config . Discovery . ResponseObjectSigningAlgs ) ) )
}
}
2023-05-15 00:03:19 +00:00
2023-05-22 11:14:32 +00:00
func validateOIDCIssuerPrivateKeysUseAlg ( i int , props * JWKProperties , config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-05-15 00:32:10 +00:00
switch config . IssuerPrivateKeys [ i ] . Use {
case "" :
config . IssuerPrivateKeys [ i ] . Use = props . Use
case oidc . KeyUseSignature :
break
default :
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysInvalidOptionOneOf , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID , attrOIDCKeyUse , strJoinOr ( [ ] string { oidc . KeyUseSignature } ) , config . IssuerPrivateKeys [ i ] . Use ) )
}
2023-05-15 00:03:19 +00:00
2023-05-15 00:32:10 +00:00
switch {
case config . IssuerPrivateKeys [ i ] . Algorithm == "" :
config . IssuerPrivateKeys [ i ] . Algorithm = props . Algorithm
2023-05-22 11:14:32 +00:00
fallthrough
2023-05-15 00:32:10 +00:00
case utils . IsStringInSlice ( config . IssuerPrivateKeys [ i ] . Algorithm , validOIDCIssuerJWKSigningAlgs ) :
2023-05-22 11:14:32 +00:00
if config . IssuerPrivateKeys [ i ] . KeyID != "" && config . IssuerPrivateKeys [ i ] . Algorithm != "" {
if _ , ok := config . Discovery . DefaultKeyIDs [ config . IssuerPrivateKeys [ i ] . Algorithm ] ; ! ok {
config . Discovery . DefaultKeyIDs [ config . IssuerPrivateKeys [ i ] . Algorithm ] = config . IssuerPrivateKeys [ i ] . KeyID
}
}
2023-05-15 00:32:10 +00:00
default :
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysInvalidOptionOneOf , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID , attrOIDCAlgorithm , strJoinOr ( validOIDCIssuerJWKSigningAlgs ) , config . IssuerPrivateKeys [ i ] . Algorithm ) )
}
2023-05-15 00:03:19 +00:00
2023-05-15 00:32:10 +00:00
if config . IssuerPrivateKeys [ i ] . Algorithm != "" {
2023-05-22 11:14:32 +00:00
if ! utils . IsStringInSlice ( config . IssuerPrivateKeys [ i ] . Algorithm , config . Discovery . ResponseObjectSigningAlgs ) {
2023-05-15 00:32:10 +00:00
config . Discovery . ResponseObjectSigningAlgs = append ( config . Discovery . ResponseObjectSigningAlgs , config . IssuerPrivateKeys [ i ] . Algorithm )
}
}
}
2023-05-15 00:03:19 +00:00
2023-05-22 11:14:32 +00:00
func validateOIDCIssuerPrivateKeyPair ( i int , config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-05-15 00:32:10 +00:00
var (
checkEqualKey bool
err error
)
2023-05-15 00:03:19 +00:00
2023-05-15 00:32:10 +00:00
switch key := config . IssuerPrivateKeys [ i ] . Key . ( type ) {
case * rsa . PrivateKey :
checkEqualKey = true
2023-05-15 00:03:19 +00:00
2023-05-15 00:32:10 +00:00
if key . Size ( ) < 256 {
checkEqualKey = false
2023-05-15 00:03:19 +00:00
2023-05-15 00:32:10 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysRSAKeyLessThan2048Bits , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID , key . Size ( ) * 8 ) )
2023-05-15 00:03:19 +00:00
}
2023-05-15 00:32:10 +00:00
case * ecdsa . PrivateKey :
checkEqualKey = true
default :
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysKeyNotRSAOrECDSA , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID , key ) )
2023-05-15 00:03:19 +00:00
}
2023-05-15 00:32:10 +00:00
if config . IssuerPrivateKeys [ i ] . CertificateChain . HasCertificates ( ) {
if checkEqualKey && ! config . IssuerPrivateKeys [ i ] . CertificateChain . EqualKey ( config . IssuerPrivateKeys [ i ] . Key ) {
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysKeyCertificateMismatch , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID ) )
}
if err = config . IssuerPrivateKeys [ i ] . CertificateChain . Validate ( ) ; err != nil {
val . Push ( fmt . Errorf ( errFmtOIDCProviderPrivateKeysCertificateChainInvalid , i + 1 , config . IssuerPrivateKeys [ i ] . KeyID , err ) )
}
2023-05-15 00:03:19 +00:00
}
}
2023-05-22 11:14:32 +00:00
func setOIDCDefaults ( config * schema . OpenIDConnect ) {
2022-10-02 02:07:40 +00:00
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
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCOptionsCORS ( config * schema . OpenIDConnect , validator * schema . StructValidator ) {
2022-04-07 00:58:51 +00:00
validateOIDCOptionsCORSAllowedOrigins ( config , validator )
if config . CORS . AllowedOriginsFromClientRedirectURIs {
validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs ( config )
}
validateOIDCOptionsCORSEndpoints ( config , validator )
}
2023-05-22 11:14:32 +00:00
func validateOIDCOptionsCORSAllowedOrigins ( config * schema . OpenIDConnect , 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
}
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCOptionsCORSAllowedOriginsFromClientRedirectURIs ( config * schema . OpenIDConnect ) {
2022-04-07 00:58:51 +00:00
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
}
}
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCOptionsCORSEndpoints ( config * schema . OpenIDConnect , 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
2023-05-22 11:14:32 +00:00
func validateOIDCClients ( config * schema . OpenIDConnect , 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-05-22 11:14:32 +00:00
func validateOIDCClient ( c int , config * schema . OpenIDConnect , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
2023-04-13 10:58:18 +00:00
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-05-14 23:51:59 +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-05-25 13:28:21 +00:00
switch {
case config . Clients [ c ] . Policy == "" :
2023-04-13 10:58:18 +00:00
config . Clients [ c ] . Policy = schema . DefaultOpenIDConnectClientConfiguration . Policy
2023-05-25 13:28:21 +00:00
case utils . IsStringInSlice ( config . Clients [ c ] . Policy , config . Discovery . Policies ) :
2023-04-13 10:58:18 +00:00
break
default :
2023-05-25 13:28:21 +00:00
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue , config . Clients [ c ] . ID , "authorization_policy" , strJoinOr ( config . Discovery . Policies ) , config . Clients [ c ] . Policy ) )
2023-04-13 10:58:18 +00:00
}
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-05-15 00:03:19 +00:00
validateOIDDClientSigningAlgs ( c , config , val )
2023-04-13 10:58:18 +00:00
validateOIDCClientSectorIdentifier ( c , config , val )
2023-05-15 00:32:10 +00:00
validateOIDCClientPublicKeys ( c , config , val )
validateOIDCClientTokenEndpointAuth ( c , config , val )
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientPublicKeys ( c int , config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-05-15 00:32:10 +00:00
switch {
case config . Clients [ c ] . PublicKeys . URI != nil && len ( config . Clients [ c ] . PublicKeys . Values ) != 0 :
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysBothURIAndValuesConfigured , config . Clients [ c ] . ID ) )
case config . Clients [ c ] . PublicKeys . URI != nil :
if config . Clients [ c ] . PublicKeys . URI . Scheme != schemeHTTPS {
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysURIInvalidScheme , config . Clients [ c ] . ID , config . Clients [ c ] . PublicKeys . URI . Scheme ) )
}
case len ( config . Clients [ c ] . PublicKeys . Values ) != 0 :
validateOIDCClientJSONWebKeysList ( c , config , val )
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientJSONWebKeysList ( c int , config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-05-15 00:32:10 +00:00
var (
props * JWKProperties
err error
)
for i := 0 ; i < len ( config . Clients [ c ] . PublicKeys . Values ) ; i ++ {
if config . Clients [ c ] . PublicKeys . Values [ i ] . KeyID == "" {
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysInvalidOptionMissingOneOf , config . Clients [ c ] . ID , i + 1 , attrOIDCKeyID ) )
}
if props , err = schemaJWKGetProperties ( config . Clients [ c ] . PublicKeys . Values [ i ] ) ; err != nil {
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysProperties , config . Clients [ c ] . ID , i + 1 , config . Clients [ c ] . PublicKeys . Values [ i ] . KeyID , err ) )
continue
}
validateOIDCClientJSONWebKeysListKeyUseAlg ( c , i , props , config , val )
var checkEqualKey bool
switch key := config . Clients [ c ] . PublicKeys . Values [ i ] . Key . ( type ) {
case nil :
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysInvalidOptionMissingOneOf , config . Clients [ c ] . ID , i + 1 , attrOIDCKey ) )
case * rsa . PublicKey :
checkEqualKey = true
if key . N == nil {
checkEqualKey = false
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysKeyMalformed , config . Clients [ c ] . ID , i + 1 ) )
} else if key . Size ( ) < 256 {
checkEqualKey = false
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysRSAKeyLessThan2048Bits , config . Clients [ c ] . ID , i + 1 , config . Clients [ c ] . PublicKeys . Values [ i ] . KeyID , key . Size ( ) * 8 ) )
}
case * ecdsa . PublicKey :
checkEqualKey = true
default :
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysKeyNotRSAOrECDSA , config . Clients [ c ] . ID , i + 1 , config . Clients [ c ] . PublicKeys . Values [ i ] . KeyID , key ) )
}
if config . Clients [ c ] . PublicKeys . Values [ i ] . CertificateChain . HasCertificates ( ) {
if checkEqualKey && ! config . Clients [ c ] . PublicKeys . Values [ i ] . CertificateChain . EqualKey ( config . Clients [ c ] . PublicKeys . Values [ i ] . Key ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysCertificateChainKeyMismatch , config . Clients [ c ] . ID , i + 1 , config . Clients [ c ] . PublicKeys . Values [ i ] . KeyID ) )
}
if err = config . Clients [ c ] . PublicKeys . Values [ i ] . CertificateChain . Validate ( ) ; err != nil {
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysCertificateChainInvalid , config . Clients [ c ] . ID , i + 1 , config . Clients [ c ] . PublicKeys . Values [ i ] . KeyID , err ) )
}
}
}
if config . Clients [ c ] . RequestObjectSigningAlg != "" && ! utils . IsStringInSlice ( config . Clients [ c ] . RequestObjectSigningAlg , config . Clients [ c ] . Discovery . RequestObjectSigningAlgs ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysROSAMissingAlgorithm , config . Clients [ c ] . ID , strJoinOr ( config . Clients [ c ] . Discovery . RequestObjectSigningAlgs ) ) )
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientJSONWebKeysListKeyUseAlg ( c , i int , props * JWKProperties , config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-05-15 00:32:10 +00:00
switch config . Clients [ c ] . PublicKeys . Values [ i ] . Use {
case "" :
config . Clients [ c ] . PublicKeys . Values [ i ] . Use = props . Use
case oidc . KeyUseSignature :
break
default :
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysInvalidOptionOneOf , config . Clients [ c ] . ID , i + 1 , config . Clients [ c ] . PublicKeys . Values [ i ] . KeyID , attrOIDCKeyUse , strJoinOr ( [ ] string { oidc . KeyUseSignature } ) , config . Clients [ c ] . PublicKeys . Values [ i ] . Use ) )
}
switch {
case config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm == "" :
config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm = props . Algorithm
case utils . IsStringInSlice ( config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm , validOIDCIssuerJWKSigningAlgs ) :
break
default :
val . Push ( fmt . Errorf ( errFmtOIDCClientPublicKeysInvalidOptionOneOf , config . Clients [ c ] . ID , i + 1 , config . Clients [ c ] . PublicKeys . Values [ i ] . KeyID , attrOIDCAlgorithm , strJoinOr ( validOIDCIssuerJWKSigningAlgs ) , config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm ) )
}
if config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm != "" {
if ! utils . IsStringInSlice ( config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm , config . Discovery . RequestObjectSigningAlgs ) {
config . Discovery . RequestObjectSigningAlgs = append ( config . Discovery . RequestObjectSigningAlgs , config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm )
}
if ! utils . IsStringInSlice ( config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm , config . Clients [ c ] . Discovery . RequestObjectSigningAlgs ) {
config . Clients [ c ] . Discovery . RequestObjectSigningAlgs = append ( config . Clients [ c ] . Discovery . RequestObjectSigningAlgs , config . Clients [ c ] . PublicKeys . Values [ i ] . Algorithm )
}
}
2021-05-04 22:06:05 +00:00
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientSectorIdentifier ( c int , config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-04-13 10:58:18 +00:00
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
}
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientConsentMode ( c int , config * schema . OpenIDConnect , val * schema . StructValidator ) {
2022-11-23 23:16:23 +00:00
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-05-22 11:14:32 +00:00
func validateOIDCClientScopes ( c int , config * schema . OpenIDConnect , 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-05-22 11:14:32 +00:00
func validateOIDCClientResponseTypes ( c int , config * schema . OpenIDConnect , 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-05-22 11:14:32 +00:00
func validateOIDCClientResponseModes ( c int , config * schema . OpenIDConnect , 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 ) ) )
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientGrantTypes ( c int , config * schema . OpenIDConnect , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
2023-04-13 10:58:18 +00:00
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 ) ) )
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientGrantTypesSetDefaults ( c int , config * schema . OpenIDConnect ) {
2023-04-13 10:58:18 +00:00
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-05-22 11:14:32 +00:00
func validateOIDCClientGrantTypesCheckRelated ( c int , config * schema . OpenIDConnect , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
2023-04-13 10:58:18 +00:00
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-05-22 11:14:32 +00:00
func validateOIDCClientRedirectURIs ( c int , config * schema . OpenIDConnect , val * schema . StructValidator , errDeprecatedFunc func ( ) ) {
2023-04-13 10:58:18 +00:00
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-05-22 11:14:32 +00:00
func validateOIDCClientTokenEndpointAuth ( c int , config * schema . OpenIDConnect , 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-05-14 23:51:59 +00:00
switch config . Clients [ c ] . TokenEndpointAuthMethod {
case "" :
break
case oidc . ClientAuthMethodClientSecretJWT :
2023-05-15 00:32:10 +00:00
validateOIDCClientTokenEndpointAuthClientSecretJWT ( c , config , val )
case oidc . ClientAuthMethodPrivateKeyJWT :
validateOIDCClientTokenEndpointAuthPublicKeyJWT ( config . Clients [ c ] , val )
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientTokenEndpointAuthClientSecretJWT ( c int , config * schema . OpenIDConnect , val * schema . StructValidator ) {
2023-05-15 00:32:10 +00:00
switch {
case config . Clients [ c ] . TokenEndpointAuthSigningAlg == "" :
config . Clients [ c ] . TokenEndpointAuthSigningAlg = oidc . SigningAlgHMACUsingSHA256
case ! utils . IsStringInSlice ( config . Clients [ c ] . TokenEndpointAuthSigningAlg , validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT ) :
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidTokenEndpointAuthSigAlg , config . Clients [ c ] . ID , strJoinOr ( validOIDCClientTokenEndpointAuthSigAlgsClientSecretJWT ) , config . Clients [ c ] . TokenEndpointAuthMethod ) )
}
}
2023-05-22 11:14:32 +00:00
func validateOIDCClientTokenEndpointAuthPublicKeyJWT ( config schema . OpenIDConnectClient , val * schema . StructValidator ) {
2023-05-15 00:32:10 +00:00
switch {
case config . TokenEndpointAuthSigningAlg == "" :
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidTokenEndpointAuthSigAlgMissingPrivateKeyJWT , config . ID ) )
case ! utils . IsStringInSlice ( config . TokenEndpointAuthSigningAlg , validOIDCIssuerJWKSigningAlgs ) :
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidTokenEndpointAuthSigAlg , config . ID , strJoinOr ( validOIDCIssuerJWKSigningAlgs ) , config . TokenEndpointAuthMethod ) )
}
if config . PublicKeys . URI == nil {
if len ( config . PublicKeys . Values ) == 0 {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidPublicKeysPrivateKeyJWT , config . ID ) )
} else if len ( config . Discovery . RequestObjectSigningAlgs ) != 0 && ! utils . IsStringInSlice ( config . TokenEndpointAuthSigningAlg , config . Discovery . RequestObjectSigningAlgs ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidTokenEndpointAuthSigAlgReg , config . ID , strJoinOr ( config . Discovery . RequestObjectSigningAlgs ) , config . TokenEndpointAuthMethod ) )
2023-05-14 23:51:59 +00:00
}
}
2023-04-13 10:58:18 +00:00
}
2023-05-22 11:14:32 +00:00
func validateOIDDClientSigningAlgs ( c int , config * schema . OpenIDConnect , val * schema . StructValidator ) {
switch config . Clients [ c ] . UserinfoSigningKeyID {
case "" :
if config . Clients [ c ] . UserinfoSigningAlg == "" {
config . Clients [ c ] . UserinfoSigningAlg = schema . DefaultOpenIDConnectClientConfiguration . UserinfoSigningAlg
} else if config . Clients [ c ] . UserinfoSigningAlg != oidc . SigningAlgNone && ! utils . IsStringInSlice ( config . Clients [ c ] . UserinfoSigningAlg , config . Discovery . ResponseObjectSigningAlgs ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue ,
config . Clients [ c ] . ID , attrOIDCUsrSigAlg , strJoinOr ( append ( config . Discovery . ResponseObjectSigningAlgs , oidc . SigningAlgNone ) ) , config . Clients [ c ] . UserinfoSigningAlg ) )
}
default :
if ! utils . IsStringInSlice ( config . Clients [ c ] . UserinfoSigningKeyID , config . Discovery . ResponseObjectSigningKeyIDs ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue ,
config . Clients [ c ] . ID , attrOIDCUsrSigKID , strJoinOr ( config . Discovery . ResponseObjectSigningKeyIDs ) , config . Clients [ c ] . UserinfoSigningKeyID ) )
} else {
config . Clients [ c ] . UserinfoSigningAlg = getResponseObjectAlgFromKID ( config , config . Clients [ c ] . UserinfoSigningKeyID , config . Clients [ c ] . UserinfoSigningAlg )
}
2023-04-13 10:58:18 +00:00
}
2023-05-22 11:14:32 +00:00
switch config . Clients [ c ] . IDTokenSigningKeyID {
case "" :
if config . Clients [ c ] . IDTokenSigningAlg == "" {
config . Clients [ c ] . IDTokenSigningAlg = schema . DefaultOpenIDConnectClientConfiguration . IDTokenSigningAlg
} else if ! utils . IsStringInSlice ( config . Clients [ c ] . IDTokenSigningAlg , config . Discovery . ResponseObjectSigningAlgs ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue ,
config . Clients [ c ] . ID , attrOIDCIDTokenSigAlg , strJoinOr ( config . Discovery . ResponseObjectSigningAlgs ) , config . Clients [ c ] . IDTokenSigningAlg ) )
}
default :
if ! utils . IsStringInSlice ( config . Clients [ c ] . IDTokenSigningKeyID , config . Discovery . ResponseObjectSigningKeyIDs ) {
val . Push ( fmt . Errorf ( errFmtOIDCClientInvalidValue ,
config . Clients [ c ] . ID , attrOIDCIDTokenSigKID , strJoinOr ( config . Discovery . ResponseObjectSigningKeyIDs ) , config . Clients [ c ] . IDTokenSigningKeyID ) )
} else {
config . Clients [ c ] . IDTokenSigningAlg = getResponseObjectAlgFromKID ( config , config . Clients [ c ] . IDTokenSigningKeyID , config . Clients [ c ] . IDTokenSigningAlg )
}
2023-04-13 10:58:18 +00:00
}
2021-05-04 22:06:05 +00:00
}