authelia/internal/configuration/validator/authentication.go
Clément Michaud cc6650dbcd
[BUGFIX] [BREAKING] Set username retrieved from authentication backend in session. (#687)
* [BUGFIX] Set username retrieved from authentication backend in session.

In some setups, binding is case insensitive but Authelia is case
sensitive and therefore need the actual username as stored in the
authentication backend in order for Authelia to work correctly.

Fixes #561.

* Use uid attribute as unique user identifier in suites.

* Fix the integration tests.

* Update config.template.yml

* Compute user filter based on username attribute and users_filter.

The filter provided in users_filter is now combined with a filter
based on the username attribute to perform the LDAP search query
finding a user object from the username.

* Fix LDAP based integration tests.

* Update `users_filter` reference examples
2020-03-15 18:10:25 +11:00

168 lines
6.8 KiB
Go

package validator
import (
"errors"
"fmt"
"net/url"
"strings"
"github.com/authelia/authelia/internal/configuration/schema"
)
var ldapProtocolPrefix = "ldap://"
var ldapsProtocolPrefix = "ldaps://"
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`"))
}
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))
}
}
}
}
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 ""
}
if !(u.Scheme == "ldap" || u.Scheme == "ldaps") {
validator.Push(errors.New("Unknown scheme for ldap url, should be ldap:// or ldaps://"))
return ""
}
if u.Scheme == "ldap" && u.Port() == "" {
u.Host += ":389"
} else if u.Scheme == "ldaps" && u.Port() == "" {
u.Host += ":636"
}
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()
}
func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
if configuration.URL == "" {
validator.Push(errors.New("Please provide a URL to the LDAP server"))
} else {
configuration.URL = validateLdapURL(configuration.URL, validator)
}
if configuration.User == "" {
validator.Push(errors.New("Please provide a user name to connect to the LDAP server"))
}
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"))
}
if configuration.UsersFilter == "" {
validator.Push(errors.New("Please provide a users filter with `users_filter` attribute"))
} else {
if !strings.HasPrefix(configuration.UsersFilter, "(") || !strings.HasSuffix(configuration.UsersFilter, ")") {
validator.Push(errors.New("The users filter should contain enclosing parenthesis. For instance uid={0} should be (uid={0})"))
}
}
if configuration.GroupsFilter == "" {
validator.Push(errors.New("Please provide a groups filter with `groups_filter` attribute"))
} else {
if !strings.HasPrefix(configuration.GroupsFilter, "(") || !strings.HasSuffix(configuration.GroupsFilter, ")") {
validator.Push(errors.New("The groups filter should contain enclosing parenthesis. For instance cn={0} should be (cn={0})"))
}
}
if configuration.UsernameAttribute == "" {
validator.Push(errors.New("Please provide a username attribute with `username_attribute`"))
}
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)
}
}