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"
2019-04-25 04:52:08 +07:00
)
2020-04-09 08:05: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
}
//Salt Length
2020-04-11 10:54:18 +07:00
if configuration . Password . SaltLength == 0 {
configuration . Password . SaltLength = schema . DefaultPasswordConfiguration . SaltLength
} else if configuration . Password . SaltLength < 2 {
validator . Push ( fmt . Errorf ( "The salt length must be 2 or more, you configured %d" , configuration . Password . SaltLength ) )
} else if configuration . Password . SaltLength > 16 {
validator . Push ( fmt . Errorf ( "The salt length must be 16 or less, 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
}
2019-12-06 15:15:54 +07: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-30 03:16:38 +07:00
}
2020-05-02 23:20:40 +07:00
if ! ( u . Scheme == schemeLDAP || u . Scheme == schemeLDAPS ) {
2019-12-06 15:15:54 +07:00
validator . Push ( errors . New ( "Unknown scheme for ldap url, should be ldap:// or ldaps://" ) )
return ""
}
2019-10-30 03:16:38 +07:00
2020-05-02 23:20:40 +07:00
if u . Scheme == schemeLDAP && u . Port ( ) == "" {
2019-12-06 15:15:54 +07:00
u . Host += ":389"
2020-05-02 23:20:40 +07:00
} else if u . Scheme == schemeLDAPS && u . Port ( ) == "" {
2019-12-06 15:15:54 +07:00
u . Host += ":636"
2019-10-30 03:16:38 +07:00
}
2019-12-06 15:15:54 +07: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-30 03:16:38 +07:00
}
2020-04-09 08:05: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 ) {
if configuration . URL == "" {
validator . Push ( errors . New ( "Please provide a URL to the LDAP server" ) )
2019-10-30 03:16:38 +07:00
} else {
configuration . URL = validateLdapURL ( configuration . URL , validator )
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-03-31 05:36:04 +07: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 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" ) )
} else {
if ! strings . HasPrefix ( configuration . GroupsFilter , "(" ) || ! strings . HasSuffix ( configuration . GroupsFilter , ")" ) {
2020-03-31 05:36:04 +07:00
validator . Push ( errors . New ( "The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})" ) )
2020-03-15 14:10:25 +07:00
}
2019-04-25 04:52:08 +07:00
}
2020-03-15 14:10:25 +07:00
if configuration . UsernameAttribute == "" {
validator . Push ( errors . New ( "Please provide a username attribute with `username_attribute`" ) )
2019-12-09 05:21:55 +07:00
}
2019-04-25 04:52:08 +07:00
if configuration . GroupNameAttribute == "" {
configuration . GroupNameAttribute = "cn"
}
if configuration . MailAttribute == "" {
configuration . MailAttribute = "mail"
}
}
// 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 )
}
}