[FEATURE] Support MSAD password reset via unicodePwd attribute (#1460)
* Added `ActiveDirectory` suite for integration tests with Samba AD * Updated documentation * Minor styling refactor to suites * Clean up LDAP user provisioning * Fix Authelia home splash to reference correct link for webmail * Add notification message for password complexity errors * Add password complexity integration test * Rename implementation default from rfc to custom * add specific defaults for LDAP (activedirectory implementation) * add docs to show the new defaults * add docs explaining the importance of users filter * add tests * update instances of LDAP implementation names to use the new consts where applicable * made the 'custom' case in the UpdatePassword method for the implementation switch the default case instead * update config examples due to the new defaults * apply changes from code review * replace schema default name from MSAD to ActiveDirectory for consistency * fix missing default for username_attribute * replace test raising on empty username attribute with not raising on empty Co-authored-by: James Elliott <james-d-elliott@users.noreply.github.com>pull/1491/head
parent
ffde77bdfd
commit
aa64d0c4e5
|
@ -8,15 +8,20 @@ cat << EOF
|
||||||
retry:
|
retry:
|
||||||
automatic: true
|
automatic: true
|
||||||
EOF
|
EOF
|
||||||
if [[ "${SUITE_NAME}" != "Kubernetes" ]]; then
|
if [[ "${SUITE_NAME}" = "ActiveDirectory" ]]; then
|
||||||
cat << EOF
|
cat << EOF
|
||||||
agents:
|
agents:
|
||||||
suite: "all"
|
suite: "activedirectory"
|
||||||
EOF
|
EOF
|
||||||
else
|
elif [[ "${SUITE_NAME}" = "Kubernetes" ]]; then
|
||||||
cat << EOF
|
cat << EOF
|
||||||
agents:
|
agents:
|
||||||
suite: "kubernetes"
|
suite: "kubernetes"
|
||||||
EOF
|
EOF
|
||||||
|
else
|
||||||
|
cat << EOF
|
||||||
|
agents:
|
||||||
|
suite: "all"
|
||||||
|
EOF
|
||||||
fi
|
fi
|
||||||
done
|
done
|
|
@ -93,6 +93,18 @@ authentication_backend:
|
||||||
# than one instance and therefore is recommended for
|
# than one instance and therefore is recommended for
|
||||||
# production.
|
# production.
|
||||||
ldap:
|
ldap:
|
||||||
|
# The LDAP implementation, this affects elements like the attribute utilised for resetting a password.
|
||||||
|
# Acceptable options are as follows:
|
||||||
|
# - 'activedirectory' - For Microsoft Active Directory.
|
||||||
|
# - 'custom' - For custom specifications of attributes and filters.
|
||||||
|
# This currently defaults to 'custom' to maintain existing behaviour.
|
||||||
|
#
|
||||||
|
# Depending on the option here certain other values in this section have a default value, notably all
|
||||||
|
# of the attribute mappings have a default value that this config overrides, you can read more
|
||||||
|
# about these default values at https://docs.authelia.com/configuration/authentication/ldap.html#defaults
|
||||||
|
|
||||||
|
implementation: custom
|
||||||
|
|
||||||
# The url to the ldap server. Scheme can be ldap:// or ldaps://
|
# The url to the ldap server. Scheme can be ldap:// or ldaps://
|
||||||
url: ldap://127.0.0.1
|
url: ldap://127.0.0.1
|
||||||
|
|
||||||
|
@ -113,7 +125,7 @@ authentication_backend:
|
||||||
# for that user. Technically, non-unique attributes like 'mail' can also be used but we don't recommend using
|
# for that user. Technically, non-unique attributes like 'mail' can also be used but we don't recommend using
|
||||||
# them, we instead advise to use the attributes mentioned above (sAMAccountName and uid) to follow
|
# them, we instead advise to use the attributes mentioned above (sAMAccountName and uid) to follow
|
||||||
# https://www.ietf.org/rfc/rfc2307.txt.
|
# https://www.ietf.org/rfc/rfc2307.txt.
|
||||||
username_attribute: uid
|
# username_attribute: uid
|
||||||
|
|
||||||
# An additional dn to define the scope to all users
|
# An additional dn to define the scope to all users
|
||||||
additional_users_dn: ou=users
|
additional_users_dn: ou=users
|
||||||
|
@ -147,14 +159,14 @@ authentication_backend:
|
||||||
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||||
|
|
||||||
# The attribute holding the name of the group
|
# The attribute holding the name of the group
|
||||||
group_name_attribute: cn
|
# group_name_attribute: cn
|
||||||
|
|
||||||
# The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only the first
|
# The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only the first
|
||||||
# one returned by the LDAP server is used.
|
# one returned by the LDAP server is used.
|
||||||
mail_attribute: mail
|
# mail_attribute: mail
|
||||||
|
|
||||||
# The attribute holding the display name of the user. This will be used to greet an authenticated user.
|
# The attribute holding the display name of the user. This will be used to greet an authenticated user.
|
||||||
display_name_attribute: displayname
|
# display_name_attribute: displayname
|
||||||
|
|
||||||
# The username and password of the admin user.
|
# The username and password of the admin user.
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
|
|
|
@ -15,6 +15,11 @@ nav_order: 2
|
||||||
Configuration of the LDAP backend is done as follows
|
Configuration of the LDAP backend is done as follows
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
# The authentication backend to use for verifying user passwords
|
||||||
|
# and retrieve information such as email address and groups
|
||||||
|
# users belong to.
|
||||||
|
#
|
||||||
|
# There are two supported backends: 'ldap' and 'file'.
|
||||||
authentication_backend:
|
authentication_backend:
|
||||||
# Disable both the HTML element and the API for reset password functionality
|
# Disable both the HTML element and the API for reset password functionality
|
||||||
disable_reset_password: false
|
disable_reset_password: false
|
||||||
|
@ -28,16 +33,32 @@ authentication_backend:
|
||||||
# Refresh Interval docs: https://docs.authelia.com/configuration/authentication/ldap.html#refresh-interval
|
# Refresh Interval docs: https://docs.authelia.com/configuration/authentication/ldap.html#refresh-interval
|
||||||
refresh_interval: 5m
|
refresh_interval: 5m
|
||||||
|
|
||||||
|
# LDAP backend configuration.
|
||||||
|
#
|
||||||
|
# This backend allows Authelia to be scaled to more
|
||||||
|
# than one instance and therefore is recommended for
|
||||||
|
# production.
|
||||||
ldap:
|
ldap:
|
||||||
|
# The LDAP implementation, this affects elements like the attribute utilised for resetting a password.
|
||||||
|
# Acceptable options are as follows:
|
||||||
|
# - 'activedirectory' - For Microsoft Active Directory.
|
||||||
|
# - 'custom' - For custom specifications of attributes and filters.
|
||||||
|
# This currently defaults to 'custom' to maintain existing behaviour.
|
||||||
|
#
|
||||||
|
# Depending on the option here certain other values in this section have a default value, notably all
|
||||||
|
# of the attribute mappings have a default value that this config overrides, you can read more
|
||||||
|
# about these default values at https://docs.authelia.com/configuration/authentication/ldap.html#defaults
|
||||||
|
implementation: custom
|
||||||
|
|
||||||
# The url to the ldap server. Scheme can be ldap:// or ldaps://
|
# The url to the ldap server. Scheme can be ldap:// or ldaps://
|
||||||
url: ldap://127.0.0.1
|
url: ldap://127.0.0.1
|
||||||
|
|
||||||
# Skip verifying the server certificate (to allow self-signed certificate).
|
# Skip verifying the server certificate (to allow self-signed certificate).
|
||||||
skip_verify: false
|
skip_verify: false
|
||||||
|
|
||||||
# The base dn for every entries
|
# The base dn for every entries
|
||||||
base_dn: dc=example,dc=com
|
base_dn: dc=example,dc=com
|
||||||
|
|
||||||
# The attribute holding the username of the user. This attribute is used to populate
|
# The attribute holding the username of the user. This attribute is used to populate
|
||||||
# the username in the session information. It was introduced due to #561 to handle case
|
# the username in the session information. It was introduced due to #561 to handle case
|
||||||
# insensitive search queries.
|
# insensitive search queries.
|
||||||
|
@ -49,11 +70,11 @@ authentication_backend:
|
||||||
# for that user. Technically, non-unique attributes like 'mail' can also be used but we don't recommend using
|
# for that user. Technically, non-unique attributes like 'mail' can also be used but we don't recommend using
|
||||||
# them, we instead advise to use the attributes mentioned above (sAMAccountName and uid) to follow
|
# them, we instead advise to use the attributes mentioned above (sAMAccountName and uid) to follow
|
||||||
# https://www.ietf.org/rfc/rfc2307.txt.
|
# https://www.ietf.org/rfc/rfc2307.txt.
|
||||||
username_attribute: uid
|
# username_attribute: uid
|
||||||
|
|
||||||
# An additional dn to define the scope to all users
|
# An additional dn to define the scope to all users
|
||||||
additional_users_dn: ou=users
|
additional_users_dn: ou=users
|
||||||
|
|
||||||
# The users filter used in search queries to find the user profile based on input filled in login form.
|
# The users filter used in search queries to find the user profile based on input filled in login form.
|
||||||
# Various placeholders are available to represent the user input and back reference other options of the configuration:
|
# Various placeholders are available to represent the user input and back reference other options of the configuration:
|
||||||
# - {input} is a placeholder replaced by what the user inputs in the login form.
|
# - {input} is a placeholder replaced by what the user inputs in the login form.
|
||||||
|
@ -68,7 +89,7 @@ authentication_backend:
|
||||||
# To allow sign in both with username and email, one can use a filter like
|
# To allow sign in both with username and email, one can use a filter like
|
||||||
# (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
|
# (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
|
||||||
users_filter: (&({username_attribute}={input})(objectClass=person))
|
users_filter: (&({username_attribute}={input})(objectClass=person))
|
||||||
|
|
||||||
# An additional dn to define the scope of groups
|
# An additional dn to define the scope of groups
|
||||||
additional_groups_dn: ou=groups
|
additional_groups_dn: ou=groups
|
||||||
|
|
||||||
|
@ -81,20 +102,19 @@ authentication_backend:
|
||||||
# - DON'T USE - {0} is an alias for {input} supported for backward compatibility but it will be deprecated in later versions, so please don't use it.
|
# - DON'T USE - {0} is an alias for {input} supported for backward compatibility but it will be deprecated in later versions, so please don't use it.
|
||||||
# - DON'T USE - {1} is an alias for {username} supported for backward compatibility but it will be deprecated in later version, so please don't use it.
|
# - DON'T USE - {1} is an alias for {username} supported for backward compatibility but it will be deprecated in later version, so please don't use it.
|
||||||
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
groups_filter: (&(member={dn})(objectclass=groupOfNames))
|
||||||
|
|
||||||
# The attribute holding the name of the group
|
|
||||||
group_name_attribute: cn
|
|
||||||
|
|
||||||
# The attribute holding the mail address of the user
|
|
||||||
mail_attribute: mail
|
|
||||||
|
|
||||||
# The attribute holding the display name of the user. This will be used to greet an authenticated user.
|
|
||||||
display_name_attribute: displayname
|
|
||||||
|
|
||||||
# The username and password of the admin user. If multiple email addresses are defined for a user, only the first
|
# The attribute holding the name of the group
|
||||||
|
# group_name_attribute: cn
|
||||||
|
|
||||||
|
# The attribute holding the mail address of the user. If multiple email addresses are defined for a user, only the first
|
||||||
# one returned by the LDAP server is used.
|
# one returned by the LDAP server is used.
|
||||||
|
# mail_attribute: mail
|
||||||
|
|
||||||
|
# The attribute holding the display name of the user. This will be used to greet an authenticated user.
|
||||||
|
# display_name_attribute: displayname
|
||||||
|
|
||||||
|
# The username and password of the admin user.
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
|
|
||||||
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
# Password can also be set using a secret: https://docs.authelia.com/configuration/secrets.html
|
||||||
password: password
|
password: password
|
||||||
```
|
```
|
||||||
|
@ -103,6 +123,39 @@ The user must have an email address in order for Authelia to perform
|
||||||
identity verification when a user attempts to reset their password or
|
identity verification when a user attempts to reset their password or
|
||||||
register a second factor device.
|
register a second factor device.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
There are currently two implementations, `custom` and `activedirectory`. The `activedirectory` implementation
|
||||||
|
must be used if you wish to allow users to change or reset their password as Active Directory
|
||||||
|
uses a custom attribute for this, and an input format other implementations do not use. The long term
|
||||||
|
intention of this is to have logical defaults for various RFC implementations of LDAP.
|
||||||
|
|
||||||
|
### Defaults
|
||||||
|
|
||||||
|
The below tables describes the current attribute defaults for each implementation.
|
||||||
|
|
||||||
|
#### Attributes
|
||||||
|
This table describes the attribute defaults for each implementation. i.e. the username_attribute is
|
||||||
|
described by the Username column.
|
||||||
|
|
||||||
|
|Implementation |Username |Display Name|Mail|Group Name|
|
||||||
|
|:-------------:|:------------:|:----------:|:--:|:--------:|
|
||||||
|
|custom |n/a |displayname |mail|cn |
|
||||||
|
|activedirectory|sAMAccountName|displayname |mail|cn |
|
||||||
|
|
||||||
|
#### Filters
|
||||||
|
|
||||||
|
The filters are probably the most important part to get correct when setting up LDAP.
|
||||||
|
You want to exclude disabled accounts. The active directory example has two attribute
|
||||||
|
filters that accomplish this as an example (more examples would be appreciated). The
|
||||||
|
userAccountControl filter checks that the account is not disabled and the pwdLastSet
|
||||||
|
makes sure that value is not 0 which means the password requires changing at the next login.
|
||||||
|
|
||||||
|
|Implementation |Users Filter |Groups Filter|
|
||||||
|
|:-------------:|:------------:|:-----------:|
|
||||||
|
|custom |n/a |n/a |
|
||||||
|
|activedirectory|(&(|({username_attribute}={input})({mail_attribute}={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))|(&(member={dn})(objectClass=group)(objectCategory=group))|
|
||||||
|
|
||||||
|
|
||||||
## Refresh Interval
|
## Refresh Interval
|
||||||
|
|
||||||
|
@ -129,7 +182,7 @@ be guaranteed by the administrator to be unique. If multiple users have the same
|
||||||
fail authenticating the user and display an error message in the logs.
|
fail authenticating the user and display an error message in the logs.
|
||||||
|
|
||||||
In order to avoid such problems, we highly recommended you follow https://www.ietf.org/rfc/rfc2307.txt by using
|
In order to avoid such problems, we highly recommended you follow https://www.ietf.org/rfc/rfc2307.txt by using
|
||||||
`sAMAccountName` for Microsoft Active Directory and `uid` for other implementations as the attribute holding the
|
`sAMAccountName` for Active Directory and `uid` for other implementations as the attribute holding the
|
||||||
unique identifier for your users.
|
unique identifier for your users.
|
||||||
|
|
||||||
## Loading a password from a secret instead of inside the configuration
|
## Loading a password from a secret instead of inside the configuration
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"golang.org/x/text/encoding/unicode"
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/configuration/schema"
|
"github.com/authelia/authelia/internal/configuration/schema"
|
||||||
"github.com/authelia/authelia/internal/logging"
|
"github.com/authelia/authelia/internal/logging"
|
||||||
|
@ -284,7 +285,16 @@ func (p *LDAPUserProvider) UpdatePassword(inputUsername string, newPassword stri
|
||||||
|
|
||||||
modifyRequest := ldap.NewModifyRequest(profile.DN, nil)
|
modifyRequest := ldap.NewModifyRequest(profile.DN, nil)
|
||||||
|
|
||||||
modifyRequest.Replace("userPassword", []string{newPassword})
|
switch p.configuration.Implementation {
|
||||||
|
case schema.LDAPImplementationActiveDirectory:
|
||||||
|
utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)
|
||||||
|
// The password needs to be enclosed in quotes
|
||||||
|
// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2
|
||||||
|
pwdEncoded, _ := utf16.NewEncoder().String(fmt.Sprintf("\"%s\"", newPassword))
|
||||||
|
modifyRequest.Replace("unicodePwd", []string{pwdEncoded})
|
||||||
|
default:
|
||||||
|
modifyRequest.Replace("userPassword", []string{newPassword})
|
||||||
|
}
|
||||||
|
|
||||||
err = client.Modify(modifyRequest)
|
err = client.Modify(modifyRequest)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package schema
|
||||||
|
|
||||||
// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server.
|
// LDAPAuthenticationBackendConfiguration represents the configuration related to LDAP server.
|
||||||
type LDAPAuthenticationBackendConfiguration struct {
|
type LDAPAuthenticationBackendConfiguration struct {
|
||||||
|
Implementation string `mapstructure:"implementation"`
|
||||||
URL string `mapstructure:"url"`
|
URL string `mapstructure:"url"`
|
||||||
SkipVerify bool `mapstructure:"skip_verify"`
|
SkipVerify bool `mapstructure:"skip_verify"`
|
||||||
BaseDN string `mapstructure:"base_dn"`
|
BaseDN string `mapstructure:"base_dn"`
|
||||||
|
@ -70,7 +71,19 @@ var DefaultPasswordSHA512Configuration = PasswordConfiguration{
|
||||||
|
|
||||||
// DefaultLDAPAuthenticationBackendConfiguration represents the default LDAP config.
|
// DefaultLDAPAuthenticationBackendConfiguration represents the default LDAP config.
|
||||||
var DefaultLDAPAuthenticationBackendConfiguration = LDAPAuthenticationBackendConfiguration{
|
var DefaultLDAPAuthenticationBackendConfiguration = LDAPAuthenticationBackendConfiguration{
|
||||||
|
Implementation: LDAPImplementationCustom,
|
||||||
|
UsernameAttribute: "uid",
|
||||||
MailAttribute: "mail",
|
MailAttribute: "mail",
|
||||||
DisplayNameAttribute: "displayname",
|
DisplayNameAttribute: "displayname",
|
||||||
GroupNameAttribute: "cn",
|
GroupNameAttribute: "cn",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration represents the default LDAP config for the MSAD Implementation.
|
||||||
|
var DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration = LDAPAuthenticationBackendConfiguration{
|
||||||
|
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))",
|
||||||
|
UsernameAttribute: "sAMAccountName",
|
||||||
|
MailAttribute: "mail",
|
||||||
|
DisplayNameAttribute: "displayName",
|
||||||
|
GroupsFilter: "(&(member={dn})(objectClass=group))",
|
||||||
|
GroupNameAttribute: "cn",
|
||||||
|
}
|
||||||
|
|
|
@ -19,3 +19,9 @@ const RefreshIntervalDefault = "5m"
|
||||||
|
|
||||||
// RefreshIntervalAlways represents the duration value refresh interval should have if set to always.
|
// RefreshIntervalAlways represents the duration value refresh interval should have if set to always.
|
||||||
const RefreshIntervalAlways = 0 * time.Millisecond
|
const RefreshIntervalAlways = 0 * time.Millisecond
|
||||||
|
|
||||||
|
// LDAPImplementationCustom is the string for the custom LDAP implementation.
|
||||||
|
const LDAPImplementationCustom = "custom"
|
||||||
|
|
||||||
|
// LDAPImplementationActiveDirectory is the string for the Active Directory LDAP implementation.
|
||||||
|
const LDAPImplementationActiveDirectory = "activedirectory"
|
||||||
|
|
|
@ -100,6 +100,19 @@ func validateLdapURL(ldapURL string, validator *schema.StructValidator) string {
|
||||||
|
|
||||||
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting.
|
//nolint:gocyclo // TODO: Consider refactoring/simplifying, time permitting.
|
||||||
func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationBackendConfiguration, validator *schema.StructValidator) {
|
||||||
|
if configuration.Implementation == "" {
|
||||||
|
configuration.Implementation = schema.DefaultLDAPAuthenticationBackendConfiguration.Implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
if configuration.URL == "" {
|
if configuration.URL == "" {
|
||||||
validator.Push(errors.New("Please provide a URL to the LDAP server"))
|
validator.Push(errors.New("Please provide a URL to the LDAP server"))
|
||||||
} else {
|
} else {
|
||||||
|
@ -143,6 +156,38 @@ func validateLdapAuthenticationBackend(configuration *schema.LDAPAuthenticationB
|
||||||
if configuration.UsernameAttribute == "" {
|
if configuration.UsernameAttribute == "" {
|
||||||
validator.Push(errors.New("Please provide a username attribute with `username_attribute`"))
|
validator.Push(errors.New("Please provide a username attribute with `username_attribute`"))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
if configuration.GroupNameAttribute == "" {
|
if configuration.GroupNameAttribute == "" {
|
||||||
configuration.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.GroupNameAttribute
|
configuration.GroupNameAttribute = schema.DefaultLDAPAuthenticationBackendConfiguration.GroupNameAttribute
|
||||||
|
|
|
@ -16,7 +16,7 @@ func TestShouldRaiseErrorsWhenNoBackendProvided(t *testing.T) {
|
||||||
|
|
||||||
ValidateAuthenticationBackend(&backendConfig, validator)
|
ValidateAuthenticationBackend(&backendConfig, validator)
|
||||||
|
|
||||||
assert.Len(t, validator.Errors(), 1)
|
require.Len(t, validator.Errors(), 1)
|
||||||
assert.EqualError(t, validator.Errors()[0], "Please provide `ldap` or `file` object in `authentication_backend`")
|
assert.EqualError(t, validator.Errors()[0], "Please provide `ldap` or `file` object in `authentication_backend`")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldValidateCompleteConfigura
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenNoPathProvided() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenNoPathProvided() {
|
||||||
suite.configuration.File.Path = ""
|
suite.configuration.File.Path = ""
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a `path` for the users database in `authentication_backend`")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a `path` for the users database in `authentication_backend`")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenMemoryNotMo
|
||||||
suite.configuration.File.Password.Memory = 8
|
suite.configuration.File.Password.Memory = 8
|
||||||
suite.configuration.File.Password.Parallelism = 2
|
suite.configuration.File.Password.Parallelism = 2
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Memory for argon2id must be 16 or more (parallelism * 8), you configured memory as 8 and parallelism as 2")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Memory for argon2id must be 16 or more (parallelism * 8), you configured memory as 8 and parallelism as 2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,35 +98,35 @@ func (suite *FileBasedAuthenticationBackend) TestShouldSetDefaultConfigurationWh
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenKeyLengthTooLow() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenKeyLengthTooLow() {
|
||||||
suite.configuration.File.Password.KeyLength = 1
|
suite.configuration.File.Password.KeyLength = 1
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Key length for argon2id must be 16, you configured 1")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Key length for argon2id must be 16, you configured 1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSaltLengthTooLow() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenSaltLengthTooLow() {
|
||||||
suite.configuration.File.Password.SaltLength = -1
|
suite.configuration.File.Password.SaltLength = -1
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The salt length must be 2 or more, you configured -1")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The salt length must be 2 or more, you configured -1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorithmDefined() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenBadAlgorithmDefined() {
|
||||||
suite.configuration.File.Password.Algorithm = "bogus"
|
suite.configuration.File.Password.Algorithm = "bogus"
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured 'bogus'")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Unknown hashing algorithm supplied, valid values are argon2id and sha512, you configured 'bogus'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenIterationsTooLow() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenIterationsTooLow() {
|
||||||
suite.configuration.File.Password.Iterations = -1
|
suite.configuration.File.Password.Iterations = -1
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The number of iterations specified is invalid, must be 1 or more, you configured -1")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The number of iterations specified is invalid, must be 1 or more, you configured -1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenParallelismTooLow() {
|
func (suite *FileBasedAuthenticationBackend) TestShouldRaiseErrorWhenParallelismTooLow() {
|
||||||
suite.configuration.File.Password.Parallelism = -1
|
suite.configuration.File.Password.Parallelism = -1
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Parallelism for argon2id must be 1 or more, you configured -1")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Parallelism for argon2id must be 1 or more, you configured -1")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,6 +159,7 @@ func (suite *LdapAuthenticationBackendSuite) SetupTest() {
|
||||||
suite.validator = schema.NewStructValidator()
|
suite.validator = schema.NewStructValidator()
|
||||||
suite.configuration = schema.AuthenticationBackendConfiguration{}
|
suite.configuration = schema.AuthenticationBackendConfiguration{}
|
||||||
suite.configuration.Ldap = &schema.LDAPAuthenticationBackendConfiguration{}
|
suite.configuration.Ldap = &schema.LDAPAuthenticationBackendConfiguration{}
|
||||||
|
suite.configuration.Ldap.Implementation = schema.LDAPImplementationCustom
|
||||||
suite.configuration.Ldap.URL = "ldap://ldap"
|
suite.configuration.Ldap.URL = "ldap://ldap"
|
||||||
suite.configuration.Ldap.User = "user"
|
suite.configuration.Ldap.User = "user"
|
||||||
suite.configuration.Ldap.Password = "password"
|
suite.configuration.Ldap.Password = "password"
|
||||||
|
@ -173,31 +174,38 @@ func (suite *LdapAuthenticationBackendSuite) TestShouldValidateCompleteConfigura
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementationIsInvalidMSAD() {
|
||||||
|
suite.configuration.Ldap.Implementation = "masd"
|
||||||
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "authentication backend ldap implementation must be blank or one of the following values `custom`, `activedirectory`")
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenURLNotProvided() {
|
||||||
suite.configuration.Ldap.URL = ""
|
suite.configuration.Ldap.URL = ""
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a URL to the LDAP server")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a URL to the LDAP server")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenUserNotProvided() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenUserNotProvided() {
|
||||||
suite.configuration.Ldap.User = ""
|
suite.configuration.Ldap.User = ""
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a user name to connect to the LDAP server")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a user name to connect to the LDAP server")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenPasswordNotProvided() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenPasswordNotProvided() {
|
||||||
suite.configuration.Ldap.Password = ""
|
suite.configuration.Ldap.Password = ""
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a password to connect to the LDAP server")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a password to connect to the LDAP server")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenBaseDNNotProvided() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseErrorWhenBaseDNNotProvided() {
|
||||||
suite.configuration.Ldap.BaseDN = ""
|
suite.configuration.Ldap.BaseDN = ""
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a base DN to connect to the LDAP server")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a base DN to connect to the LDAP server")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,11 +223,10 @@ func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseOnEmptyUsersFilter()
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a users filter with `users_filter` attribute")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a users filter with `users_filter` attribute")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseOnEmptyUsernameAttribute() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() {
|
||||||
suite.configuration.Ldap.UsernameAttribute = ""
|
suite.configuration.Ldap.UsernameAttribute = ""
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
require.Len(suite.T(), suite.validator.Errors(), 1)
|
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Please provide a username attribute with `username_attribute`")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval() {
|
||||||
|
@ -229,6 +236,12 @@ func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseOnBadRefreshInterval
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Auth Backend `refresh_interval` is configured to 'blah' but it must be either a duration notation or one of 'disable', or 'always'. Error from parser: Could not convert the input string of blah into a duration")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "Auth Backend `refresh_interval` is configured to 'blah' but it must be either a duration notation or one of 'disable', or 'always'. Error from parser: Could not convert the input string of blah into a duration")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultImplementation() {
|
||||||
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
|
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||||
|
assert.Equal(suite.T(), schema.LDAPImplementationCustom, suite.configuration.Ldap.Implementation)
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttribute() {
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||||
|
@ -237,34 +250,40 @@ func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttrib
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() {
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
require.Len(suite.T(), suite.validator.Errors(), 0)
|
||||||
assert.Equal(suite.T(), "mail", suite.configuration.Ldap.MailAttribute)
|
assert.Equal(suite.T(), "mail", suite.configuration.Ldap.MailAttribute)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() {
|
||||||
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
|
require.Len(suite.T(), suite.validator.Errors(), 0)
|
||||||
|
assert.Equal(suite.T(), "displayname", suite.configuration.Ldap.DisplayNameAttribute)
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
require.Len(suite.T(), suite.validator.Errors(), 0)
|
||||||
assert.Equal(suite.T(), "5m", suite.configuration.RefreshInterval)
|
assert.Equal(suite.T(), "5m", suite.configuration.RefreshInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesNotContainEnclosingParenthesis() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseWhenUsersFilterDoesNotContainEnclosingParenthesis() {
|
||||||
suite.configuration.Ldap.UsersFilter = "uid={input}"
|
suite.configuration.Ldap.UsersFilter = "uid={input}"
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The users filter should contain enclosing parenthesis. For instance uid={input} should be (uid={input})")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The users filter should contain enclosing parenthesis. For instance uid={input} should be (uid={input})")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseWhenGroupsFilterDoesNotContainEnclosingParenthesis() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldRaiseWhenGroupsFilterDoesNotContainEnclosingParenthesis() {
|
||||||
suite.configuration.Ldap.GroupsFilter = "cn={input}"
|
suite.configuration.Ldap.GroupsFilter = "cn={input}"
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "The groups filter should contain enclosing parenthesis. For instance cn={input} should be (cn={input})")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (suite *LdapAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlaceholder() {
|
func (suite *LdapAuthenticationBackendSuite) TestShouldHelpDetectNoInputPlaceholder() {
|
||||||
suite.configuration.Ldap.UsersFilter = "(objectClass=person)"
|
suite.configuration.Ldap.UsersFilter = "(objectClass=person)"
|
||||||
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
assert.Len(suite.T(), suite.validator.Errors(), 1)
|
require.Len(suite.T(), suite.validator.Errors(), 1)
|
||||||
assert.EqualError(suite.T(), suite.validator.Errors()[0], "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")
|
assert.EqualError(suite.T(), suite.validator.Errors()[0], "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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,3 +308,79 @@ func (suite *LdapAuthenticationBackendSuite) TestShouldAdaptLDAPURL() {
|
||||||
func TestLdapAuthenticationBackend(t *testing.T) {
|
func TestLdapAuthenticationBackend(t *testing.T) {
|
||||||
suite.Run(t, new(LdapAuthenticationBackendSuite))
|
suite.Run(t, new(LdapAuthenticationBackendSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ActiveDirectoryAuthenticationBackendSuite struct {
|
||||||
|
suite.Suite
|
||||||
|
configuration schema.AuthenticationBackendConfiguration
|
||||||
|
validator *schema.StructValidator
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
|
||||||
|
suite.validator = schema.NewStructValidator()
|
||||||
|
suite.configuration = schema.AuthenticationBackendConfiguration{}
|
||||||
|
suite.configuration.Ldap = &schema.LDAPAuthenticationBackendConfiguration{}
|
||||||
|
suite.configuration.Ldap.Implementation = schema.LDAPImplementationActiveDirectory
|
||||||
|
suite.configuration.Ldap.URL = "ldap://ldap"
|
||||||
|
suite.configuration.Ldap.User = "user"
|
||||||
|
suite.configuration.Ldap.Password = "password"
|
||||||
|
suite.configuration.Ldap.BaseDN = "base_dn"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirectoryDefaults() {
|
||||||
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
|
|
||||||
|
assert.Len(suite.T(), suite.validator.Errors(), 0)
|
||||||
|
|
||||||
|
assert.Equal(suite.T(),
|
||||||
|
suite.configuration.Ldap.UsersFilter,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter)
|
||||||
|
assert.Equal(suite.T(),
|
||||||
|
suite.configuration.Ldap.UsernameAttribute,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute)
|
||||||
|
assert.Equal(suite.T(),
|
||||||
|
suite.configuration.Ldap.DisplayNameAttribute,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute)
|
||||||
|
assert.Equal(suite.T(),
|
||||||
|
suite.configuration.Ldap.MailAttribute,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute)
|
||||||
|
assert.Equal(suite.T(),
|
||||||
|
suite.configuration.Ldap.GroupsFilter,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter)
|
||||||
|
assert.Equal(suite.T(),
|
||||||
|
suite.configuration.Ldap.GroupNameAttribute,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
|
||||||
|
suite.configuration.Ldap.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
|
||||||
|
suite.configuration.Ldap.UsernameAttribute = "cn"
|
||||||
|
suite.configuration.Ldap.MailAttribute = "userPrincipalName"
|
||||||
|
suite.configuration.Ldap.DisplayNameAttribute = "name"
|
||||||
|
suite.configuration.Ldap.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
|
||||||
|
suite.configuration.Ldap.GroupNameAttribute = "distinguishedName"
|
||||||
|
|
||||||
|
ValidateAuthenticationBackend(&suite.configuration, suite.validator)
|
||||||
|
|
||||||
|
assert.NotEqual(suite.T(),
|
||||||
|
suite.configuration.Ldap.UsersFilter,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsersFilter)
|
||||||
|
assert.NotEqual(suite.T(),
|
||||||
|
suite.configuration.Ldap.UsernameAttribute,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.UsernameAttribute)
|
||||||
|
assert.NotEqual(suite.T(),
|
||||||
|
suite.configuration.Ldap.DisplayNameAttribute,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.DisplayNameAttribute)
|
||||||
|
assert.NotEqual(suite.T(),
|
||||||
|
suite.configuration.Ldap.MailAttribute,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.MailAttribute)
|
||||||
|
assert.NotEqual(suite.T(),
|
||||||
|
suite.configuration.Ldap.GroupsFilter,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupsFilter)
|
||||||
|
assert.NotEqual(suite.T(),
|
||||||
|
suite.configuration.Ldap.GroupNameAttribute,
|
||||||
|
schema.DefaultLDAPAuthenticationBackendImplementationActiveDirectoryConfiguration.GroupNameAttribute)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActiveDirectoryAuthenticationBackend(t *testing.T) {
|
||||||
|
suite.Run(t, new(ActiveDirectoryAuthenticationBackendSuite))
|
||||||
|
}
|
||||||
|
|
|
@ -91,6 +91,7 @@ var validKeys = []string{
|
||||||
"authentication_backend.refresh_interval",
|
"authentication_backend.refresh_interval",
|
||||||
|
|
||||||
// LDAP Authentication Backend Keys.
|
// LDAP Authentication Backend Keys.
|
||||||
|
"authentication_backend.ldap.implementation",
|
||||||
"authentication_backend.ldap.url",
|
"authentication_backend.ldap.url",
|
||||||
"authentication_backend.ldap.skip_verify",
|
"authentication_backend.ldap.skip_verify",
|
||||||
"authentication_backend.ldap.base_dn",
|
"authentication_backend.ldap.base_dn",
|
||||||
|
|
|
@ -37,6 +37,8 @@ const unableToRegisterSecurityKeyMessage = "Unable to register your security key
|
||||||
const unableToResetPasswordMessage = "Unable to reset your password."
|
const unableToResetPasswordMessage = "Unable to reset your password."
|
||||||
const mfaValidationFailedMessage = "Authentication failed, please retry later."
|
const mfaValidationFailedMessage = "Authentication failed, please retry later."
|
||||||
|
|
||||||
|
const ldapPasswordComplexityCode = "0000052D"
|
||||||
|
|
||||||
const testInactivity = "10"
|
const testInactivity = "10"
|
||||||
const testRedirectionURL = "http://redirection.local"
|
const testRedirectionURL = "http://redirection.local"
|
||||||
const testResultAllow = "allow"
|
const testResultAllow = "allow"
|
||||||
|
|
|
@ -2,6 +2,7 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/middlewares"
|
"github.com/authelia/authelia/internal/middlewares"
|
||||||
)
|
)
|
||||||
|
@ -29,7 +30,13 @@ func ResetPasswordPost(ctx *middlewares.AutheliaCtx) {
|
||||||
err = ctx.Providers.UserProvider.UpdatePassword(*userSession.PasswordResetUsername, requestBody.Password)
|
err = ctx.Providers.UserProvider.UpdatePassword(*userSession.PasswordResetUsername, requestBody.Password)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to update password: %s", err), unableToResetPasswordMessage)
|
switch {
|
||||||
|
case strings.Contains(err.Error(), ldapPasswordComplexityCode):
|
||||||
|
ctx.Error(fmt.Errorf("%s", err), ldapPasswordComplexityCode)
|
||||||
|
default:
|
||||||
|
ctx.Error(fmt.Errorf("%s", err), unableToResetPasswordMessage)
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
###############################################################
|
||||||
|
# Authelia minimal configuration #
|
||||||
|
###############################################################
|
||||||
|
|
||||||
|
port: 9091
|
||||||
|
tls_cert: /config/ssl/cert.pem
|
||||||
|
tls_key: /config/ssl/key.pem
|
||||||
|
|
||||||
|
log_level: debug
|
||||||
|
|
||||||
|
default_redirection_url: https://home.example.com:8080/
|
||||||
|
|
||||||
|
jwt_secret: very_important_secret
|
||||||
|
|
||||||
|
authentication_backend:
|
||||||
|
ldap:
|
||||||
|
implementation: activedirectory
|
||||||
|
url: ldaps://sambaldap
|
||||||
|
skip_verify: true
|
||||||
|
base_dn: DC=example,DC=com
|
||||||
|
username_attribute: sAMAccountName
|
||||||
|
additional_users_dn: OU=Users
|
||||||
|
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(objectClass=user))
|
||||||
|
additional_groups_dn: OU=Groups
|
||||||
|
groups_filter: (&(member={dn})(objectClass=group))
|
||||||
|
group_name_attribute: cn
|
||||||
|
mail_attribute: mail
|
||||||
|
display_name_attribute: displayName
|
||||||
|
user: CN=Administrator,CN=Users,DC=example,DC=com
|
||||||
|
password: password
|
||||||
|
|
||||||
|
session:
|
||||||
|
secret: unsecure_session_secret
|
||||||
|
domain: example.com
|
||||||
|
expiration: 3600 # 1 hour
|
||||||
|
inactivity: 300 # 5 minutes
|
||||||
|
remember_me_duration: 1y
|
||||||
|
|
||||||
|
storage:
|
||||||
|
local:
|
||||||
|
path: /config/db.sqlite3
|
||||||
|
|
||||||
|
totp:
|
||||||
|
issuer: example.com
|
||||||
|
|
||||||
|
access_control:
|
||||||
|
default_policy: deny
|
||||||
|
rules:
|
||||||
|
- domain: "public.example.com"
|
||||||
|
policy: bypass
|
||||||
|
- domain: "admin.example.com"
|
||||||
|
policy: two_factor
|
||||||
|
- domain: "secure.example.com"
|
||||||
|
policy: two_factor
|
||||||
|
- domain: "singlefactor.example.com"
|
||||||
|
policy: one_factor
|
||||||
|
|
||||||
|
regulation:
|
||||||
|
max_retries: 3
|
||||||
|
find_time: 300
|
||||||
|
ban_time: 900
|
||||||
|
|
||||||
|
notifier:
|
||||||
|
smtp:
|
||||||
|
host: smtp
|
||||||
|
port: 1025
|
||||||
|
sender: admin@example.com
|
||||||
|
disable_require_tls: true
|
|
@ -0,0 +1,6 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
authelia-backend:
|
||||||
|
volumes:
|
||||||
|
- './ActiveDirectory/configuration.yml:/config/configuration.yml:ro'
|
||||||
|
- './common/ssl:/config/ssl:ro'
|
|
@ -27,9 +27,18 @@ func (wds *WebDriverSession) doSuccessfullyCompletePasswordReset(ctx context.Con
|
||||||
wds.verifyIsFirstFactorPage(ctx, t)
|
wds.verifyIsFirstFactorPage(ctx, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wds *WebDriverSession) doResetPassword(ctx context.Context, t *testing.T, username, newPassword1, newPassword2 string) {
|
func (wds *WebDriverSession) doUnsuccessfulPasswordReset(ctx context.Context, t *testing.T, newPassword1, newPassword2 string) {
|
||||||
|
wds.doCompletePasswordReset(ctx, t, newPassword1, newPassword2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wds *WebDriverSession) doResetPassword(ctx context.Context, t *testing.T, username, newPassword1, newPassword2 string, unsuccessful bool) {
|
||||||
wds.doInitiatePasswordReset(ctx, t, username)
|
wds.doInitiatePasswordReset(ctx, t, username)
|
||||||
// then wait for the "email sent notification"
|
// then wait for the "email sent notification"
|
||||||
wds.verifyMailNotificationDisplayed(ctx, t)
|
wds.verifyMailNotificationDisplayed(ctx, t)
|
||||||
wds.doSuccessfullyCompletePasswordReset(ctx, t, newPassword1, newPassword2)
|
|
||||||
|
if unsuccessful {
|
||||||
|
wds.doUnsuccessfulPasswordReset(ctx, t, newPassword1, newPassword2)
|
||||||
|
} else {
|
||||||
|
wds.doSuccessfullyCompletePasswordReset(ctx, t, newPassword1, newPassword2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,16 @@ func waitUntilAutheliaFrontendIsReady(dockerEnvironment *DockerEnvironment) erro
|
||||||
[]string{"You can now view web in the browser.", "Compiled with warnings", "Compiled successfully!"})
|
[]string{"You can now view web in the browser.", "Compiled with warnings", "Compiled successfully!"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment) error {
|
func waitUntilSambaIsReady(dockerEnvironment *DockerEnvironment) error {
|
||||||
|
return waitUntilServiceLogDetected(
|
||||||
|
5*time.Second,
|
||||||
|
90*time.Second,
|
||||||
|
dockerEnvironment,
|
||||||
|
"sambaldap",
|
||||||
|
[]string{"samba entered RUNNING state"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment, suite string) error {
|
||||||
log.Info("Waiting for Authelia to be ready...")
|
log.Info("Waiting for Authelia to be ready...")
|
||||||
|
|
||||||
if err := waitUntilAutheliaBackendIsReady(dockerEnvironment); err != nil {
|
if err := waitUntilAutheliaBackendIsReady(dockerEnvironment); err != nil {
|
||||||
|
@ -71,6 +80,12 @@ func waitUntilAutheliaIsReady(dockerEnvironment *DockerEnvironment) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if suite == "ActiveDirectory" {
|
||||||
|
if err := waitUntilSambaIsReady(dockerEnvironment); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("Authelia is now ready!")
|
log.Info("Authelia is now ready!")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -65,13 +65,3 @@ sn: Dean
|
||||||
uid: james
|
uid: james
|
||||||
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
|
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
|
||||||
|
|
||||||
dn: cn=Billy Blackhat,ou=users,dc=example,dc=com
|
|
||||||
cn: Billy Blackhat
|
|
||||||
displayname: Billy Blackhat
|
|
||||||
givenName: Billy
|
|
||||||
objectclass: inetOrgPerson
|
|
||||||
objectclass: top
|
|
||||||
mail: billy.blackhat@authelia.com
|
|
||||||
sn: BlackHat
|
|
||||||
uid: blackhat
|
|
||||||
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
|
|
||||||
|
|
|
@ -68,7 +68,7 @@
|
||||||
<br /> Once first factor is passed, you will need to follow the links to register a secret for the second
|
<br /> Once first factor is passed, you will need to follow the links to register a secret for the second
|
||||||
factor.<br /> Authelia
|
factor.<br /> Authelia
|
||||||
will send you a fictitious email in a <strong>fake webmail</strong> at <a
|
will send you a fictitious email in a <strong>fake webmail</strong> at <a
|
||||||
href="http://localhost:8085">http://localhost:8085</a>.<br />
|
href="https://mail.example.com:8080/">https://mail.example.com:8080/</a>.<br />
|
||||||
It will provide you with the link to complete the registration allowing you to authenticate with 2-factor.
|
It will provide you with the link to complete the registration allowing you to authenticate with 2-factor.
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
FROM alpine:3.12.1
|
||||||
|
|
||||||
|
RUN \
|
||||||
|
apk add --no-cache \
|
||||||
|
bash \
|
||||||
|
krb5 \
|
||||||
|
openldap-clients \
|
||||||
|
samba-dc \
|
||||||
|
supervisor
|
||||||
|
|
||||||
|
ADD init.sh /init.sh
|
||||||
|
CMD /init.sh setup
|
|
@ -0,0 +1,14 @@
|
||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
sambaldap:
|
||||||
|
build:
|
||||||
|
context: ./example/compose/samba
|
||||||
|
cap_add:
|
||||||
|
- SYS_ADMIN
|
||||||
|
hostname: ldap.example.com
|
||||||
|
environment:
|
||||||
|
- DOMAIN=example.com
|
||||||
|
- DOMAINPASS=Password1
|
||||||
|
- NOCOMPLEXITY=true
|
||||||
|
networks:
|
||||||
|
- authelianet
|
|
@ -0,0 +1,103 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
appSetup () {
|
||||||
|
|
||||||
|
# Set variables
|
||||||
|
DOMAIN=${DOMAIN:-SAMDOM.LOCAL}
|
||||||
|
DOMAINPASS=${DOMAINPASS:-youshouldsetapassword}
|
||||||
|
NOCOMPLEXITY=${NOCOMPLEXITY:-false}
|
||||||
|
INSECURELDAP=${INSECURELDAP:-false}
|
||||||
|
|
||||||
|
LDOMAIN=${DOMAIN,,}
|
||||||
|
UDOMAIN=${DOMAIN^^}
|
||||||
|
URDOMAIN=${UDOMAIN%%.*}
|
||||||
|
|
||||||
|
# Set up samba
|
||||||
|
mv /etc/krb5.conf /etc/krb5.conf.orig
|
||||||
|
echo "[libdefaults]" > /etc/krb5.conf
|
||||||
|
echo " dns_lookup_realm = false" >> /etc/krb5.conf
|
||||||
|
echo " dns_lookup_kdc = true" >> /etc/krb5.conf
|
||||||
|
echo " default_realm = ${UDOMAIN}" >> /etc/krb5.conf
|
||||||
|
# If the finished file isn't there, this is brand new, we're not just moving to a new container
|
||||||
|
if [[ ! -f /etc/samba/external/smb.conf ]]; then
|
||||||
|
mv /etc/samba/smb.conf /etc/samba/smb.conf.orig
|
||||||
|
samba-tool domain provision --use-rfc2307 --domain=${URDOMAIN} --realm=${UDOMAIN} --server-role=dc --dns-backend=SAMBA_INTERNAL --adminpass=${DOMAINPASS}
|
||||||
|
if [[ ${NOCOMPLEXITY,,} == "true" ]]; then
|
||||||
|
samba-tool domain passwordsettings set --complexity=off
|
||||||
|
samba-tool domain passwordsettings set --history-length=0
|
||||||
|
samba-tool domain passwordsettings set --min-pwd-length=3
|
||||||
|
samba-tool domain passwordsettings set --min-pwd-age=0
|
||||||
|
samba-tool domain passwordsettings set --max-pwd-age=0
|
||||||
|
fi
|
||||||
|
sed -i "/\[global\]/a \
|
||||||
|
\\\tidmap_ldb:use rfc2307 = yes\\n\
|
||||||
|
wins support = yes\\n\
|
||||||
|
template shell = /bin/bash\\n\
|
||||||
|
winbind nss info = rfc2307\\n\
|
||||||
|
idmap config ${URDOMAIN}: range = 10000-20000\\n\
|
||||||
|
idmap config ${URDOMAIN}: backend = ad\
|
||||||
|
" /etc/samba/smb.conf
|
||||||
|
if [[ ${INSECURELDAP,,} == "true" ]]; then
|
||||||
|
sed -i "/\[global\]/a \
|
||||||
|
\\\tldap server require strong auth = no\
|
||||||
|
" /etc/samba/smb.conf
|
||||||
|
fi
|
||||||
|
# Once we are set up, we'll make a file so that we know to use it if we ever spin this up again
|
||||||
|
mkdir -p /etc/samba/external
|
||||||
|
cp /etc/samba/smb.conf /etc/samba/external/smb.conf
|
||||||
|
else
|
||||||
|
cp /etc/samba/external/smb.conf /etc/samba/smb.conf
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set up supervisor
|
||||||
|
mkdir /etc/supervisor.d/
|
||||||
|
echo "[supervisord]" > /etc/supervisor.d/supervisord.ini
|
||||||
|
echo "nodaemon=true" >> /etc/supervisor.d/supervisord.ini
|
||||||
|
echo "" >> /etc/supervisor.d/supervisord.ini
|
||||||
|
echo "[program:samba]" >> /etc/supervisor.d/supervisord.ini
|
||||||
|
echo "command=/usr/sbin/samba -i" >> /etc/supervisor.d/supervisord.ini
|
||||||
|
|
||||||
|
appProvision
|
||||||
|
appStart
|
||||||
|
}
|
||||||
|
|
||||||
|
appStart () {
|
||||||
|
/usr/bin/supervisord
|
||||||
|
}
|
||||||
|
|
||||||
|
appProvision () {
|
||||||
|
samba-tool user setpassword administrator --newpassword=password
|
||||||
|
samba-tool ou create "OU=Users"
|
||||||
|
samba-tool ou create "OU=Groups"
|
||||||
|
samba-tool group add dev --groupou=OU=Groups
|
||||||
|
samba-tool group add admins --groupou=OU=Groups
|
||||||
|
samba-tool user create john password --userou=OU=Users --use-username-as-cn --given-name John --surname Doe --mail-address john.doe@authelia.com
|
||||||
|
samba-tool user create harry password --userou=OU=Users --use-username-as-cn --given-name Harry --surname Potter --mail-address harry.potter@authelia.com
|
||||||
|
samba-tool user create bob password --userou=OU=Users --use-username-as-cn --given-name Bob --surname Dylan --mail-address bob.dylan@authelia.com
|
||||||
|
samba-tool user create james password --userou=OU=Users --use-username-as-cn --given-name James --surname Dean --mail-address james.dean@authelia.com
|
||||||
|
samba-tool group addmembers "dev" john,bob
|
||||||
|
samba-tool group addmembers "admins" john
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
if [[ -f /etc/samba/external/smb.conf ]]; then
|
||||||
|
cp /etc/samba/external/smb.conf /etc/samba/smb.conf
|
||||||
|
appStart
|
||||||
|
else
|
||||||
|
echo "Config file is missing."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
setup)
|
||||||
|
# If the supervisor conf isn't there, we're spinning up a new container
|
||||||
|
if [[ -f /etc/supervisor.d/supervisord.ini ]]; then
|
||||||
|
appStart
|
||||||
|
else
|
||||||
|
appSetup
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,61 @@
|
||||||
|
package suites
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PasswordComplexityScenario struct {
|
||||||
|
*SeleniumSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPasswordComplexityScenario() *PasswordComplexityScenario {
|
||||||
|
return &PasswordComplexityScenario{SeleniumSuite: new(SeleniumSuite)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PasswordComplexityScenario) SetupSuite() {
|
||||||
|
wds, err := StartWebDriver()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.WebDriverSession = wds
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PasswordComplexityScenario) TearDownSuite() {
|
||||||
|
err := s.WebDriverSession.Stop()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PasswordComplexityScenario) SetupTest() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
s.doLogout(ctx, s.T())
|
||||||
|
s.doVisit(s.T(), HomeBaseURL)
|
||||||
|
s.verifyIsHome(ctx, s.T())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PasswordComplexityScenario) TestShouldRejectPasswordReset() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
s.doVisit(s.T(), GetLoginBaseURL())
|
||||||
|
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||||
|
|
||||||
|
// Attempt to reset the password to a
|
||||||
|
s.doResetPassword(ctx, s.T(), "john", "a", "a", true)
|
||||||
|
s.verifyNotificationDisplayed(ctx, s.T(), "Your supplied password does not meet the password policy requirements.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunPasswordComplexityScenario(t *testing.T) {
|
||||||
|
suite.Run(t, NewPasswordComplexityScenario())
|
||||||
|
}
|
|
@ -52,7 +52,7 @@ func (s *ResetPasswordScenario) TestShouldResetPassword() {
|
||||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||||
|
|
||||||
// Reset the password to abc
|
// Reset the password to abc
|
||||||
s.doResetPassword(ctx, s.T(), "john", "abc", "abc")
|
s.doResetPassword(ctx, s.T(), "john", "abc", "abc", false)
|
||||||
|
|
||||||
// Try to login with the old password
|
// Try to login with the old password
|
||||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||||
|
@ -65,7 +65,7 @@ func (s *ResetPasswordScenario) TestShouldResetPassword() {
|
||||||
s.doLogout(ctx, s.T())
|
s.doLogout(ctx, s.T())
|
||||||
|
|
||||||
// Reset the original password
|
// Reset the original password
|
||||||
s.doResetPassword(ctx, s.T(), "john", "password", "password")
|
s.doResetPassword(ctx, s.T(), "john", "password", "password", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ResetPasswordScenario) TestShouldMakeAttackerThinkPasswordResetIsInitiated() {
|
func (s *ResetPasswordScenario) TestShouldMakeAttackerThinkPasswordResetIsInitiated() {
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package suites
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var activedirectorySuiteName = "ActiveDirectory"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dockerEnvironment := NewDockerEnvironment([]string{
|
||||||
|
"internal/suites/docker-compose.yml",
|
||||||
|
"internal/suites/ActiveDirectory/docker-compose.yml",
|
||||||
|
"internal/suites/example/compose/authelia/docker-compose.backend.{}.yml",
|
||||||
|
"internal/suites/example/compose/authelia/docker-compose.frontend.{}.yml",
|
||||||
|
"internal/suites/example/compose/nginx/backend/docker-compose.yml",
|
||||||
|
"internal/suites/example/compose/nginx/portal/docker-compose.yml",
|
||||||
|
"internal/suites/example/compose/smtp/docker-compose.yml",
|
||||||
|
"internal/suites/example/compose/samba/docker-compose.yml",
|
||||||
|
})
|
||||||
|
|
||||||
|
setup := func(suitePath string) error {
|
||||||
|
if err := dockerEnvironment.Up(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return waitUntilAutheliaIsReady(dockerEnvironment, activedirectorySuiteName)
|
||||||
|
}
|
||||||
|
|
||||||
|
displayAutheliaLogs := func() error {
|
||||||
|
backendLogs, err := dockerEnvironment.Logs("authelia-backend", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(backendLogs)
|
||||||
|
|
||||||
|
frontendLogs, err := dockerEnvironment.Logs("authelia-frontend", nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(frontendLogs)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
teardown := func(suitePath string) error {
|
||||||
|
err := dockerEnvironment.Down()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalRegistry.Register(activedirectorySuiteName, Suite{
|
||||||
|
SetUp: setup,
|
||||||
|
SetUpTimeout: 5 * time.Minute,
|
||||||
|
OnSetupTimeout: displayAutheliaLogs,
|
||||||
|
TestTimeout: 120 * time.Second,
|
||||||
|
TearDown: teardown,
|
||||||
|
TearDownTimeout: 2 * time.Minute,
|
||||||
|
OnError: displayAutheliaLogs,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package suites
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActiveDirectorySuite struct {
|
||||||
|
*SeleniumSuite
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActiveDirectorySuite() *ActiveDirectorySuite {
|
||||||
|
return &ActiveDirectorySuite{SeleniumSuite: new(SeleniumSuite)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActiveDirectorySuite) TestOneFactorScenario() {
|
||||||
|
suite.Run(s.T(), NewOneFactorScenario())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActiveDirectorySuite) TestTwoFactorScenario() {
|
||||||
|
suite.Run(s.T(), NewTwoFactorScenario())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActiveDirectorySuite) TestResetPassword() {
|
||||||
|
suite.Run(s.T(), NewResetPasswordScenario())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActiveDirectorySuite) TestPasswordComplexity() {
|
||||||
|
suite.Run(s.T(), NewPasswordComplexityScenario())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ActiveDirectorySuite) TestSigninEmailScenario() {
|
||||||
|
suite.Run(s.T(), NewSigninEmailScenario())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActiveDirectorySuite(t *testing.T) {
|
||||||
|
suite.Run(t, NewActiveDirectorySuite())
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, bypassAllSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var dockerSuiteName = "Docker"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
dockerEnvironment := NewDockerEnvironment([]string{
|
dockerEnvironment := NewDockerEnvironment([]string{
|
||||||
"internal/suites/docker-compose.yml",
|
"internal/suites/docker-compose.yml",
|
||||||
|
@ -21,7 +23,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, dockerSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
@ -46,7 +48,7 @@ func init() {
|
||||||
return dockerEnvironment.Down()
|
return dockerEnvironment.Down()
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalRegistry.Register("Docker", Suite{
|
GlobalRegistry.Register(dockerSuiteName, Suite{
|
||||||
SetUp: setup,
|
SetUp: setup,
|
||||||
SetUpTimeout: 5 * time.Minute,
|
SetUpTimeout: 5 * time.Minute,
|
||||||
OnSetupTimeout: displayAutheliaLogs,
|
OnSetupTimeout: displayAutheliaLogs,
|
||||||
|
|
|
@ -23,7 +23,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, duoPushSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -20,13 +20,11 @@ func init() {
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
err := dockerEnvironment.Up()
|
if err := dockerEnvironment.Up(); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, haproxySuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -28,7 +28,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(haDockerEnvironment)
|
return waitUntilAutheliaIsReady(haDockerEnvironment, highAvailabilitySuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -21,13 +21,11 @@ func init() {
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
err := dockerEnvironment.Up()
|
if err := dockerEnvironment.Up(); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, ldapSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, mariadbSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, mysqlSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -26,7 +26,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, networkACLSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -22,7 +22,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, oneFactorOnlySuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -20,13 +20,11 @@ func init() {
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
err := dockerEnvironment.Up()
|
if err := dockerEnvironment.Up(); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, pathPrefixSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -25,7 +25,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, postgresSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -23,7 +23,7 @@ func init() {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, shortTimeoutsSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -25,13 +25,11 @@ func init() {
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
err := dockerEnvironment.Up()
|
if err := dockerEnvironment.Up(); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, standaloneSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -20,13 +20,11 @@ func init() {
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
err := dockerEnvironment.Up()
|
if err := dockerEnvironment.Up(); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, traefikSuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -20,13 +20,11 @@ func init() {
|
||||||
})
|
})
|
||||||
|
|
||||||
setup := func(suitePath string) error {
|
setup := func(suitePath string) error {
|
||||||
err := dockerEnvironment.Up()
|
if err := dockerEnvironment.Up(); err != nil {
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitUntilAutheliaIsReady(dockerEnvironment)
|
return waitUntilAutheliaIsReady(dockerEnvironment, traefik2SuiteName)
|
||||||
}
|
}
|
||||||
|
|
||||||
displayAutheliaLogs := func() error {
|
displayAutheliaLogs := func() error {
|
||||||
|
|
|
@ -70,7 +70,11 @@ const ResetPasswordStep2 = function () {
|
||||||
setFormDisabled(true);
|
setFormDisabled(true);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
createErrorNotification("There was an issue resetting the password.");
|
if (err.message.indexOf("0000052D")) {
|
||||||
|
createErrorNotification("Your supplied password does not meet the password policy requirements.");
|
||||||
|
} else {
|
||||||
|
createErrorNotification("There was an issue resetting the password.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue