2019-04-24 21:52:08 +00:00
package validator
import (
"errors"
2019-12-06 08:15:54 +00:00
"fmt"
"net/url"
2019-12-08 22:21:55 +00:00
"strings"
2019-04-24 21:52:08 +00:00
2019-12-24 02:14:52 +00:00
"github.com/authelia/authelia/internal/configuration/schema"
2020-05-04 19:39:25 +00:00
"github.com/authelia/authelia/internal/utils"
2019-04-24 21:52:08 +00:00
)
2020-04-09 01:05:17 +00:00
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
2019-04-24 21:52:08 +00: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 01:38:02 +00:00
2020-04-11 03:54:18 +00:00
if configuration . Password == nil {
configuration . Password = & schema . DefaultPasswordConfiguration
2020-03-06 01:38:02 +00:00
} else {
2020-04-11 03:54:18 +00:00
if configuration . Password . Algorithm == "" {
configuration . Password . Algorithm = schema . DefaultPasswordConfiguration . Algorithm
2020-03-06 01:38:02 +00:00
} else {
2020-04-11 03:54:18 +00:00
configuration . Password . Algorithm = strings . ToLower ( configuration . Password . Algorithm )
2020-05-02 16:20:40 +00:00
if configuration . Password . Algorithm != argon2id && configuration . Password . Algorithm != sha512 {
2020-04-11 03:54:18 +00:00
validator . Push ( fmt . Errorf ( "Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured '%s'" , configuration . Password . Algorithm ) )
2020-03-06 01:38:02 +00:00
}
}
// Iterations (time)
2020-04-11 03:54:18 +00:00
if configuration . Password . Iterations == 0 {
2020-05-02 16:20:40 +00:00
if configuration . Password . Algorithm == argon2id {
2020-04-11 03:54:18 +00:00
configuration . Password . Iterations = schema . DefaultPasswordConfiguration . Iterations
2020-03-06 01:38:02 +00:00
} else {
2020-04-11 03:54:18 +00:00
configuration . Password . Iterations = schema . DefaultPasswordSHA512Configuration . Iterations
2020-03-06 01:38:02 +00:00
}
2020-04-11 03:54:18 +00: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 01:38:02 +00:00
}
//Salt Length
2020-05-06 00:52:06 +00:00
switch {
case configuration . Password . SaltLength == 0 :
2020-04-11 03:54:18 +00:00
configuration . Password . SaltLength = schema . DefaultPasswordConfiguration . SaltLength
2020-05-14 05:55:03 +00:00
case configuration . Password . SaltLength < 8 :
2020-04-11 03:54:18 +00:00
validator . Push ( fmt . Errorf ( "The salt length must be 2 or more, you configured %d" , configuration . Password . SaltLength ) )
2020-03-06 01:38:02 +00:00
}
2020-05-02 16:20:40 +00:00
if configuration . Password . Algorithm == argon2id {
2020-03-06 01:38:02 +00:00
// Parallelism
2020-04-11 03:54:18 +00: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 01:38:02 +00:00
}
// Memory
2020-04-11 03:54:18 +00: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 01:38:02 +00:00
}
// Key Length
2020-04-11 03:54:18 +00: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 01:38:02 +00:00
}
}
}
2019-04-24 21:52:08 +00:00
}
2019-12-06 08:15:54 +00:00
func validateLdapURL ( ldapURL string , validator * schema . StructValidator ) string {
u , err := url . Parse ( ldapURL )
if err != nil {
validator . Push ( errors . New ( "Unable to parse URL to ldap server. The scheme is probably missing: ldap:// or ldaps://" ) )
return ""
2019-10-29 20:16:38 +00:00
}
2020-05-02 16:20:40 +00:00
if ! ( u . Scheme == schemeLDAP || u . Scheme == schemeLDAPS ) {
2019-12-06 08:15:54 +00:00
validator . Push ( errors . New ( "Unknown scheme for ldap url, should be ldap:// or ldaps://" ) )
return ""
}
2019-10-29 20:16:38 +00:00
2020-05-02 16:20:40 +00:00
if u . Scheme == schemeLDAP && u . Port ( ) == "" {
2019-12-06 08:15:54 +00:00
u . Host += ":389"
2020-05-02 16:20:40 +00:00
} else if u . Scheme == schemeLDAPS && u . Port ( ) == "" {
2019-12-06 08:15:54 +00:00
u . Host += ":636"
2019-10-29 20:16:38 +00:00
}
2019-12-06 08:15:54 +00:00
if ! u . IsAbs ( ) {
validator . Push ( fmt . Errorf ( "URL to LDAP %s is still not absolute, it should be something like ldap://127.0.0.1:389" , u . String ( ) ) )
}
return u . String ( )
2019-10-29 20:16:38 +00:00
}
2020-04-09 01:05:17 +00:00
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting
2019-04-24 21:52:08 +00:00
func validateLdapAuthenticationBackend ( configuration * schema . LDAPAuthenticationBackendConfiguration , validator * schema . StructValidator ) {
if configuration . URL == "" {
validator . Push ( errors . New ( "Please provide a URL to the LDAP server" ) )
2019-10-29 20:16:38 +00:00
} else {
configuration . URL = validateLdapURL ( configuration . URL , validator )
2019-04-24 21:52:08 +00:00
}
2020-04-04 23:28:09 +00: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-24 21:52:08 +00:00
if configuration . User == "" {
validator . Push ( errors . New ( "Please provide a user name to connect to the LDAP server" ) )
}
2020-04-04 23:28:09 +00: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-24 21:52:08 +00: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-30 22:36:04 +00:00
if configuration . UsersFilter == "" {
validator . Push ( errors . New ( "Please provide a users filter with `users_filter` attribute" ) )
} else {
2020-03-15 07:10:25 +00:00
if ! strings . HasPrefix ( configuration . UsersFilter , "(" ) || ! strings . HasSuffix ( configuration . UsersFilter , ")" ) {
2020-03-30 22:36:04 +00:00
validator . Push ( errors . New ( "The users filter should contain enclosing parenthesis. For instance uid={input} should be (uid={input})" ) )
}
// 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 07:10:25 +00:00
}
2019-12-08 22:21:55 +00:00
}
2019-04-24 21:52:08 +00:00
if configuration . GroupsFilter == "" {
2020-03-15 07:10:25 +00:00
validator . Push ( errors . New ( "Please provide a groups filter with `groups_filter` attribute" ) )
2020-05-06 00:52:06 +00: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-24 21:52:08 +00:00
}
2020-03-15 07:10:25 +00:00
if configuration . UsernameAttribute == "" {
validator . Push ( errors . New ( "Please provide a username attribute with `username_attribute`" ) )
2019-12-08 22:21:55 +00:00
}
2019-04-24 21:52:08 +00:00
if configuration . GroupNameAttribute == "" {
2020-05-04 19:39:25 +00:00
configuration . GroupNameAttribute = schema . DefaultLDAPAuthenticationBackendConfiguration . GroupNameAttribute
2019-04-24 21:52:08 +00:00
}
if configuration . MailAttribute == "" {
2020-05-04 19:39:25 +00:00
configuration . MailAttribute = schema . DefaultLDAPAuthenticationBackendConfiguration . MailAttribute
2019-04-24 21:52:08 +00:00
}
2020-06-19 10:50:21 +00:00
if configuration . DisplayNameAttribute == "" {
configuration . DisplayNameAttribute = schema . DefaultLDAPAuthenticationBackendConfiguration . DisplayNameAttribute
}
2019-04-24 21:52:08 +00: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-04 19:39:25 +00: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-24 21:52:08 +00:00
}