2019-04-25 04:52:08 +07:00
package validator
import (
"errors"
2019-12-06 15:15:54 +07:00
"fmt"
"net/url"
2019-12-09 05:21:55 +07:00
"strings"
2019-04-25 04:52:08 +07:00
2019-12-24 09:14:52 +07:00
"github.com/authelia/authelia/internal/configuration/schema"
2020-05-05 02:39:25 +07:00
"github.com/authelia/authelia/internal/utils"
2019-04-25 04:52:08 +07:00
)
2020-09-04 10:20:17 +07:00
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting.
2019-04-25 04:52:08 +07:00
func validateFileAuthenticationBackend ( configuration * schema . FileAuthenticationBackendConfiguration , validator * schema . StructValidator ) {
if configuration . Path == "" {
validator . Push ( errors . New ( "Please provide a `path` for the users database in `authentication_backend`" ) )
}
2020-03-06 08:38:02 +07:00
2020-04-11 10:54:18 +07:00
if configuration . Password == nil {
configuration . Password = & schema . DefaultPasswordConfiguration
2020-03-06 08:38:02 +07:00
} else {
2020-04-11 10:54:18 +07:00
if configuration . Password . Algorithm == "" {
configuration . Password . Algorithm = schema . DefaultPasswordConfiguration . Algorithm
2020-03-06 08:38:02 +07:00
} else {
2020-04-11 10:54:18 +07:00
configuration . Password . Algorithm = strings . ToLower ( configuration . Password . Algorithm )
2020-05-02 23:20:40 +07:00
if configuration . Password . Algorithm != argon2id && configuration . Password . Algorithm != sha512 {
2020-04-11 10:54:18 +07:00
validator . Push ( fmt . Errorf ( "Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured '%s'" , configuration . Password . Algorithm ) )
2020-03-06 08:38:02 +07:00
}
}
// Iterations (time)
2020-04-11 10:54:18 +07:00
if configuration . Password . Iterations == 0 {
2020-05-02 23:20:40 +07:00
if configuration . Password . Algorithm == argon2id {
2020-04-11 10:54:18 +07:00
configuration . Password . Iterations = schema . DefaultPasswordConfiguration . Iterations
2020-03-06 08:38:02 +07:00
} else {
2020-04-11 10:54:18 +07:00
configuration . Password . Iterations = schema . DefaultPasswordSHA512Configuration . Iterations
2020-03-06 08:38:02 +07:00
}
2020-04-11 10:54:18 +07:00
} else if configuration . Password . Iterations < 1 {
validator . Push ( fmt . Errorf ( "The number of iterations specified is invalid, must be 1 or more, you configured %d" , configuration . Password . Iterations ) )
2020-03-06 08:38:02 +07:00
}
2020-09-18 19:05:43 +07:00
// Salt Length
2020-05-06 07:52:06 +07:00
switch {
case configuration . Password . SaltLength == 0 :
2020-04-11 10:54:18 +07:00
configuration . Password . SaltLength = schema . DefaultPasswordConfiguration . SaltLength
2020-05-14 12:55:03 +07:00
case configuration . Password . SaltLength < 8 :
2020-04-11 10:54:18 +07:00
validator . Push ( fmt . Errorf ( "The salt length must be 2 or more, you configured %d" , configuration . Password . SaltLength ) )
2020-03-06 08:38:02 +07:00
}
2020-05-02 23:20:40 +07:00
if configuration . Password . Algorithm == argon2id {
2020-03-06 08:38:02 +07:00
// Parallelism
2020-04-11 10:54:18 +07:00
if configuration . Password . Parallelism == 0 {
configuration . Password . Parallelism = schema . DefaultPasswordConfiguration . Parallelism
} else if configuration . Password . Parallelism < 1 {
validator . Push ( fmt . Errorf ( "Parallelism for argon2id must be 1 or more, you configured %d" , configuration . Password . Parallelism ) )
2020-03-06 08:38:02 +07:00
}
// Memory
2020-04-11 10:54:18 +07:00
if configuration . Password . Memory == 0 {
configuration . Password . Memory = schema . DefaultPasswordConfiguration . Memory
} else if configuration . Password . Memory < configuration . Password . Parallelism * 8 {
validator . Push ( fmt . Errorf ( "Memory for argon2id must be %d or more (parallelism * 8), you configured memory as %d and parallelism as %d" , configuration . Password . Parallelism * 8 , configuration . Password . Memory , configuration . Password . Parallelism ) )
2020-03-06 08:38:02 +07:00
}
// Key Length
2020-04-11 10:54:18 +07:00
if configuration . Password . KeyLength == 0 {
configuration . Password . KeyLength = schema . DefaultPasswordConfiguration . KeyLength
} else if configuration . Password . KeyLength < 16 {
validator . Push ( fmt . Errorf ( "Key length for argon2id must be 16, you configured %d" , configuration . Password . KeyLength ) )
2020-03-06 08:38:02 +07:00
}
}
}
2019-04-25 04:52:08 +07:00
}
2021-01-04 17:28:55 +07:00
// Wrapper for test purposes to exclude the hostname from the return.
func validateLdapURLSimple ( ldapURL string , validator * schema . StructValidator ) ( finalURL string ) {
finalURL , _ = validateLdapURL ( ldapURL , validator )
return finalURL
}
func validateLdapURL ( ldapURL string , validator * schema . StructValidator ) ( finalURL string , hostname string ) {
parsedURL , err := url . Parse ( ldapURL )
2019-12-06 15:15:54 +07:00
if err != nil {
validator . Push ( errors . New ( "Unable to parse URL to ldap server. The scheme is probably missing: ldap:// or ldaps://" ) )
2021-01-04 17:28:55 +07:00
return "" , ""
2019-10-30 03:16:38 +07:00
}
2021-01-04 17:28:55 +07:00
if ! ( parsedURL . Scheme == schemeLDAP || parsedURL . Scheme == schemeLDAPS ) {
2019-12-06 15:15:54 +07:00
validator . Push ( errors . New ( "Unknown scheme for ldap url, should be ldap:// or ldaps://" ) )
2021-01-04 17:28:55 +07:00
return "" , ""
2019-10-30 03:16:38 +07:00
}
2019-12-06 15:15:54 +07:00
2021-01-04 17:28:55 +07:00
return parsedURL . String ( ) , parsedURL . Hostname ( )
2019-10-30 03:16:38 +07:00
}
2020-09-04 10:20:17 +07:00
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting.
2019-04-25 04:52:08 +07:00
func validateLdapAuthenticationBackend ( configuration * schema . LDAPAuthenticationBackendConfiguration , validator * schema . StructValidator ) {
2020-11-27 16:59:22 +07:00
if configuration . Implementation == "" {
configuration . Implementation = schema . DefaultLDAPAuthenticationBackendConfiguration . Implementation
}
2021-01-04 17:28:55 +07:00
nilTLS := configuration . TLS == nil
if nilTLS {
configuration . TLS = schema . DefaultLDAPAuthenticationBackendConfiguration . TLS
}
// Deprecated. Maps deprecated values to the new ones. TODO: Remove in 4.28 (if block).
if configuration . SkipVerify != nil {
validator . PushWarning ( errors . New ( "DEPRECATED: LDAP Auth Backend `skip_verify` option has been replaced by `authentication_backend.ldap.tls.skip_verify` (will be removed in 4.28.0)" ) )
if nilTLS {
configuration . TLS . SkipVerify = * configuration . SkipVerify
}
}
// Deprecated. Maps deprecated values to the new ones. TODO: Remove in 4.28 (if block).
if configuration . MinimumTLSVersion != "" {
validator . PushWarning ( errors . New ( "DEPRECATED: LDAP Auth Backend `minimum_tls_version` option has been replaced by `authentication_backend.ldap.tls.minimum_version` (will be removed in 4.28.0)" ) )
if nilTLS {
configuration . TLS . MinimumVersion = configuration . MinimumTLSVersion
}
}
if configuration . TLS . MinimumVersion == "" {
configuration . TLS . MinimumVersion = schema . DefaultLDAPAuthenticationBackendConfiguration . TLS . MinimumVersion
}
if _ , err := utils . TLSStringToTLSConfigVersion ( configuration . TLS . MinimumVersion ) ; err != nil {
validator . Push ( fmt . Errorf ( "error occurred validating the LDAP minimum_tls_version key with value %s: %v" , configuration . TLS . MinimumVersion , err ) )
2020-12-03 12:23:52 +07:00
}
2020-11-27 16:59:22 +07:00
switch configuration . Implementation {
case schema . LDAPImplementationCustom :
setDefaultImplementationCustomLdapAuthenticationBackend ( configuration )
case schema . LDAPImplementationActiveDirectory :
setDefaultImplementationActiveDirectoryLdapAuthenticationBackend ( configuration )
default :
validator . Push ( fmt . Errorf ( "authentication backend ldap implementation must be blank or one of the following values `%s`, `%s`" , schema . LDAPImplementationCustom , schema . LDAPImplementationActiveDirectory ) )
}
2019-04-25 04:52:08 +07:00
if configuration . URL == "" {
validator . Push ( errors . New ( "Please provide a URL to the LDAP server" ) )
2019-10-30 03:16:38 +07:00
} else {
2021-01-04 17:28:55 +07:00
ldapURL , serverName := validateLdapURL ( configuration . URL , validator )
configuration . URL = ldapURL
if configuration . TLS . ServerName == "" {
configuration . TLS . ServerName = serverName
}
2019-04-25 04:52:08 +07:00
}
2020-04-05 06:28:09 +07:00
// TODO: see if it's possible to disable this check if disable_reset_password is set and when anonymous/user binding is supported (#101 and #387)
2019-04-25 04:52:08 +07:00
if configuration . User == "" {
validator . Push ( errors . New ( "Please provide a user name to connect to the LDAP server" ) )
}
2020-04-05 06:28:09 +07:00
// TODO: see if it's possible to disable this check if disable_reset_password is set and when anonymous/user binding is supported (#101 and #387)
2019-04-25 04:52:08 +07:00
if configuration . Password == "" {
validator . Push ( errors . New ( "Please provide a password to connect to the LDAP server" ) )
}
if configuration . BaseDN == "" {
validator . Push ( errors . New ( "Please provide a base DN to connect to the LDAP server" ) )
}
2020-03-31 05:36:04 +07:00
if configuration . UsersFilter == "" {
validator . Push ( errors . New ( "Please provide a users filter with `users_filter` attribute" ) )
} else {
2020-03-15 14:10:25 +07:00
if ! strings . HasPrefix ( configuration . UsersFilter , "(" ) || ! strings . HasSuffix ( configuration . UsersFilter , ")" ) {
2020-11-27 20:30:27 +07:00
validator . Push ( errors . New ( "The users filter should contain enclosing parenthesis. For instance {username_attribute}={input} should be ({username_attribute}={input})" ) )
}
if ! strings . Contains ( configuration . UsersFilter , "{username_attribute}" ) {
validator . Push ( errors . New ( "Unable to detect {username_attribute} placeholder in users_filter, your configuration is broken. " +
"Please review configuration options listed at https://docs.authelia.com/configuration/authentication/ldap.html" ) )
2020-03-31 05:36:04 +07:00
}
// This test helps the user know that users_filter is broken after the breaking change induced by this commit.
if ! strings . Contains ( configuration . UsersFilter , "{0}" ) && ! strings . Contains ( configuration . UsersFilter , "{input}" ) {
validator . Push ( errors . New ( "Unable to detect {input} placeholder in users_filter, your configuration might be broken. " +
"Please review configuration options listed at https://docs.authelia.com/configuration/authentication/ldap.html" ) )
2020-03-15 14:10:25 +07:00
}
2019-12-09 05:21:55 +07:00
}
2019-04-25 04:52:08 +07:00
if configuration . GroupsFilter == "" {
2020-03-15 14:10:25 +07:00
validator . Push ( errors . New ( "Please provide a groups filter with `groups_filter` attribute" ) )
2020-05-06 07:52:06 +07:00
} else if ! strings . HasPrefix ( configuration . GroupsFilter , "(" ) || ! strings . HasSuffix ( configuration . GroupsFilter , ")" ) {
validator . Push ( errors . New ( "The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})" ) )
2019-04-25 04:52:08 +07:00
}
2020-11-27 16:59:22 +07:00
}
func setDefaultImplementationActiveDirectoryLdapAuthenticationBackend ( configuration * schema . LDAPAuthenticationBackendConfiguration ) {
if configuration . UsersFilter == "" {
configuration . UsersFilter = schema . DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration . UsersFilter
}
if configuration . UsernameAttribute == "" {
configuration . UsernameAttribute = schema . DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration . UsernameAttribute
}
if configuration . DisplayNameAttribute == "" {
configuration . DisplayNameAttribute = schema . DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration . DisplayNameAttribute
}
if configuration . MailAttribute == "" {
configuration . MailAttribute = schema . DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration . MailAttribute
}
if configuration . GroupsFilter == "" {
configuration . GroupsFilter = schema . DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration . GroupsFilter
}
if configuration . GroupNameAttribute == "" {
configuration . GroupNameAttribute = schema . DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration . GroupNameAttribute
}
}
func setDefaultImplementationCustomLdapAuthenticationBackend ( configuration * schema . LDAPAuthenticationBackendConfiguration ) {
if configuration . UsernameAttribute == "" {
configuration . UsernameAttribute = schema . DefaultLDAPAuthenticationBackendConfiguration . UsernameAttribute
}
2019-12-09 05:21:55 +07:00
2019-04-25 04:52:08 +07:00
if configuration . GroupNameAttribute == "" {
2020-05-05 02:39:25 +07:00
configuration . GroupNameAttribute = schema . DefaultLDAPAuthenticationBackendConfiguration . GroupNameAttribute
2019-04-25 04:52:08 +07:00
}
if configuration . MailAttribute == "" {
2020-05-05 02:39:25 +07:00
configuration . MailAttribute = schema . DefaultLDAPAuthenticationBackendConfiguration . MailAttribute
2019-04-25 04:52:08 +07:00
}
2020-06-19 17:50:21 +07:00
if configuration . DisplayNameAttribute == "" {
configuration . DisplayNameAttribute = schema . DefaultLDAPAuthenticationBackendConfiguration . DisplayNameAttribute
}
2019-04-25 04:52:08 +07:00
}
// ValidateAuthenticationBackend validates and update authentication backend configuration.
func ValidateAuthenticationBackend ( configuration * schema . AuthenticationBackendConfiguration , validator * schema . StructValidator ) {
if configuration . Ldap == nil && configuration . File == nil {
validator . Push ( errors . New ( "Please provide `ldap` or `file` object in `authentication_backend`" ) )
}
if configuration . Ldap != nil && configuration . File != nil {
validator . Push ( errors . New ( "You cannot provide both `ldap` and `file` objects in `authentication_backend`" ) )
}
if configuration . File != nil {
validateFileAuthenticationBackend ( configuration . File , validator )
} else if configuration . Ldap != nil {
validateLdapAuthenticationBackend ( configuration . Ldap , validator )
}
2020-05-05 02:39:25 +07:00
if configuration . RefreshInterval == "" {
configuration . RefreshInterval = schema . RefreshIntervalDefault
} else {
_ , err := utils . ParseDurationString ( configuration . RefreshInterval )
if err != nil && configuration . RefreshInterval != schema . ProfileRefreshDisabled && configuration . RefreshInterval != schema . ProfileRefreshAlways {
validator . Push ( fmt . Errorf ( "Auth Backend `refresh_interval` is configured to '%s' but it must be either a duration notation or one of 'disable', or 'always'. Error from parser: %s" , configuration . RefreshInterval , err ) )
}
}
2019-04-25 04:52:08 +07:00
}