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"
2019-04-24 21:52:08 +00:00
)
2019-10-29 20:16:38 +00:00
var ldapProtocolPrefix = "ldap://"
2019-12-06 08:15:54 +00:00
var ldapsProtocolPrefix = "ldaps://"
2019-10-29 20:16:38 +00:00
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
if configuration . PasswordHashing == nil {
configuration . PasswordHashing = & schema . DefaultPasswordOptionsConfiguration
} else {
if configuration . PasswordHashing . Algorithm == "" {
configuration . PasswordHashing . Algorithm = schema . DefaultPasswordOptionsConfiguration . Algorithm
} else {
configuration . PasswordHashing . Algorithm = strings . ToLower ( configuration . PasswordHashing . Algorithm )
if configuration . PasswordHashing . Algorithm != "argon2id" && configuration . PasswordHashing . Algorithm != "sha512" {
validator . Push ( fmt . Errorf ( "Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured '%s'" , configuration . PasswordHashing . Algorithm ) )
}
}
// Iterations (time)
if configuration . PasswordHashing . Iterations == 0 {
if configuration . PasswordHashing . Algorithm == "argon2id" {
configuration . PasswordHashing . Iterations = schema . DefaultPasswordOptionsConfiguration . Iterations
} else {
configuration . PasswordHashing . Iterations = schema . DefaultPasswordOptionsSHA512Configuration . Iterations
}
} else if configuration . PasswordHashing . Iterations < 1 {
validator . Push ( fmt . Errorf ( "The number of iterations specified is invalid, must be 1 or more, you configured %d" , configuration . PasswordHashing . Iterations ) )
}
//Salt Length
if configuration . PasswordHashing . SaltLength == 0 {
configuration . PasswordHashing . SaltLength = schema . DefaultPasswordOptionsConfiguration . SaltLength
} else if configuration . PasswordHashing . SaltLength < 2 {
validator . Push ( fmt . Errorf ( "The salt length must be 2 or more, you configured %d" , configuration . PasswordHashing . SaltLength ) )
} else if configuration . PasswordHashing . SaltLength > 16 {
validator . Push ( fmt . Errorf ( "The salt length must be 16 or less, you configured %d" , configuration . PasswordHashing . SaltLength ) )
}
if configuration . PasswordHashing . Algorithm == "argon2id" {
// Parallelism
if configuration . PasswordHashing . Parallelism == 0 {
configuration . PasswordHashing . Parallelism = schema . DefaultPasswordOptionsConfiguration . Parallelism
} else if configuration . PasswordHashing . Parallelism < 1 {
validator . Push ( fmt . Errorf ( "Parallelism for argon2id must be 1 or more, you configured %d" , configuration . PasswordHashing . Parallelism ) )
}
// Memory
if configuration . PasswordHashing . Memory == 0 {
configuration . PasswordHashing . Memory = schema . DefaultPasswordOptionsConfiguration . Memory
} else if configuration . PasswordHashing . Memory < configuration . PasswordHashing . 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 . PasswordHashing . Parallelism * 8 , configuration . PasswordHashing . Memory , configuration . PasswordHashing . Parallelism ) )
}
// Key Length
if configuration . PasswordHashing . KeyLength == 0 {
configuration . PasswordHashing . KeyLength = schema . DefaultPasswordOptionsConfiguration . KeyLength
} else if configuration . PasswordHashing . KeyLength < 16 {
validator . Push ( fmt . Errorf ( "Key length for argon2id must be 16, you configured %d" , configuration . PasswordHashing . KeyLength ) )
}
}
}
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
}
2019-12-06 08:15:54 +00:00
if ! ( u . Scheme == "ldap" || u . Scheme == "ldaps" ) {
validator . Push ( errors . New ( "Unknown scheme for ldap url, should be ldap:// or ldaps://" ) )
return ""
}
2019-10-29 20:16:38 +00:00
2019-12-06 08:15:54 +00:00
if u . Scheme == "ldap" && u . Port ( ) == "" {
u . Host += ":389"
} else if u . Scheme == "ldaps" && u . Port ( ) == "" {
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
}
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" ) )
} else {
if ! strings . HasPrefix ( configuration . GroupsFilter , "(" ) || ! strings . HasSuffix ( configuration . GroupsFilter , ")" ) {
2020-03-30 22:36:04 +00:00
validator . Push ( errors . New ( "The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})" ) )
2020-03-15 07:10:25 +00:00
}
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 == "" {
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 )
}
}