feat(authentication): ldap memberof group search (#5418)

Introduces the concept of group search mode into the LDAP configuration. This also adds the filter and memberof search modes. The full description of these is included in the docs but the filter mode is the same mode as previous which is also the default and recommended value. The memberof mode should only be used by users who are aware of how the concept works as per the docs.

Closes #2161

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
docs-jsonschema
James Elliott 2023-06-18 14:40:38 +10:00 committed by GitHub
parent 68ac62acab
commit f79db588be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 3160 additions and 1230 deletions

View File

@ -315,7 +315,7 @@ authentication_backend:
## because it allows Authelia to offload the stateful operations ## because it allows Authelia to offload the stateful operations
## onto the LDAP service. ## onto the LDAP service.
# ldap: # ldap:
## The address of the LDAP server to connect to in the address common syntax. ## The address of the directory server to connect to in the address common syntax.
## Format: [<scheme>://]<hostname>[:<port>]. ## Format: [<scheme>://]<hostname>[:<port>].
## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`. ## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`.
## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'. ## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'.
@ -401,16 +401,6 @@ authentication_backend:
## See also: additional_users_dn, additional_groups_dn. ## See also: additional_users_dn, additional_groups_dn.
# 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 username in the session
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually
## uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and the
## configuration stored in database. Therefore only single value attributes are allowed and the value must never be
## changed once attributed to a user otherwise it would break the configuration 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
## a filter to perform alternative lookups and the attributes mentioned above (sAMAccountName and uid) to
## follow https://datatracker.ietf.org/doc/html/rfc2307.
# username_attribute: 'uid'
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users. ## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users. ## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
# additional_users_dn: 'ou=users' # additional_users_dn: 'ou=users'
@ -441,15 +431,9 @@ authentication_backend:
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames)) ## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
# groups_filter: '(&(member={dn})(objectClass=groupOfNames))' # groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
## The attribute holding the name of the group. ## The group search mode to use. Options are 'filter' or 'memberof'. It's essential to read the docs if you wish to
# group_name_attribute: 'cn' ## use 'memberof'. Also 'filter' is the best choice for most use cases.
# group_search_mode: 'filter'
## 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.
# 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'
## Follow referrals returned by the server. ## Follow referrals returned by the server.
## This is especially useful for environments where read-only servers exist. Only implemented for write operations. ## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
@ -460,6 +444,37 @@ authentication_backend:
## Password can also be set using a secret: https://www.authelia.com/c/secrets ## Password can also be set using a secret: https://www.authelia.com/c/secrets
# password: 'password' # password: 'password'
## The attributes for users and objects from the directory server.
# attributes:
## The distinguished name attribute if your directory server supports it. Users should read the docs before
## configuring. Only used for the 'memberof' group search mode.
# distinguished_name: ''
## The attribute holding the username of the user. This attribute is used to populate the username in the session
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP
## usually uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and
## the configuration stored in database; therefore only single value attributes are allowed and the value must
## never be changed once attributed to a user otherwise it would break the configuration 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 a filter to perform alternative lookups and the attributes mentioned above
## (sAMAccountName and uid) to follow https://datatracker.ietf.org/doc/html/rfc2307.
# username: 'uid'
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
# display_name: 'displayName'
## 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 directory server is used.
# mail: 'mail'
## The attribute which provides distinguished names of groups an object is a member of.
## Only used for the 'memberof' group search mode.
# member_of: 'memberOf'
## The attribute holding the name of the group.
# group_name: 'cn'
## ##
## File (Authentication Provider) ## File (Authentication Provider)
## ##

View File

@ -101,16 +101,20 @@ authentication_backend:
base_dn: 'DC=example,DC=com' base_dn: 'DC=example,DC=com'
additional_users_dn: 'OU=users' additional_users_dn: 'OU=users'
users_filter: '(&({username_attribute}={input})(objectClass=person))' users_filter: '(&({username_attribute}={input})(objectClass=person))'
username_attribute: 'uid'
mail_attribute: 'mail'
display_name_attribute: 'displayName'
additional_groups_dn: 'OU=groups' additional_groups_dn: 'OU=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn' group_search_mode: 'filter'
permit_referrals: false permit_referrals: false
permit_unauthenticated_bind: false permit_unauthenticated_bind: false
user: 'CN=admin,DC=example,DC=com' user: 'CN=admin,DC=example,DC=com'
password: 'password' password: 'password'
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
display_name: 'displayName'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
``` ```
## Options ## Options
@ -209,66 +213,33 @@ The LDAP filter to narrow down which users are valid. This is important to set c
The default value is dependent on the [implementation](#implementation), refer to the The default value is dependent on the [implementation](#implementation), refer to the
[attribute defaults](../../reference/guides/ldap.md#attribute-defaults) for more information. [attribute defaults](../../reference/guides/ldap.md#attribute-defaults) for more information.
### username_attribute
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults](../../reference/guides/ldap.md#attribute-defaults)
for more information.*
The LDAP attribute that maps to the username in *Authelia*. This must contain the `{username_attribute}`
[placeholder](../../reference/guides/ldap.md#users-filter-replacements).
### mail_attribute
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults](../../reference/guides/ldap.md#attribute-defaults)
for more information.*
The attribute to retrieve which contains the users email addresses. This is important for the device registration and
password reset processes. The user must have an email address in order for Authelia to perform identity verification
when a user attempts to reset their password or register a second factor device.
### display_name_attribute
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults](#attribute-defaults) for more information.*
The attribute to retrieve which is shown on the Web UI to the user when they log in.
### additional_groups_dn ### additional_groups_dn
{{< confkey type="string" required="no" >}} {{< confkey type="string" required="no" >}}
Similar to [additional_users_dn](#additional_users_dn) but it applies to group searches. Similar to [additional_users_dn](#additionalusersdn) but it applies to group searches.
### groups_filter ### groups_filter
{{< confkey type="string" required="situational" >}} {{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a *__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [filter defaults](#filter-defaults) for more information.* default negating this requirement. Refer to the [filter defaults](../../reference/guides/ldap.md#filter-defaults) for
more information.*
Similar to [users_filter](#users_filter) but it applies to group searches. In order to include groups the member is not Similar to [users_filter](#usersfilter) but it applies to group searches. In order to include groups the member is not
a direct member of, but is a member of another group that is a member of those (i.e. recursive groups), you may try a direct member of, but is a member of another group that is a member of those (i.e. recursive groups), you may try
using the following filter which is currently only tested against Microsoft Active Directory: using the following filter which is currently only tested against Microsoft Active Directory:
`(&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))` `(&(member:1.2.840.113556.1.4.1941:={dn})(objectClass=group)(objectCategory=group))`
### group_name_attribute ### group_search_mode
{{< confkey type="string" required="situational" >}} {{< confkey type="string" default="filter" required="no" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a The group search mode controls how user groups are discovered. The default of `filter` directly uses the filter to
default negating this requirement. Refer to the [attribute defaults](#attribute-defaults) for more determine the result. The `memberof` experimental mode does another special filtered search. See the
information.* [Reference Documentation](../../reference/guides/ldap.md#group-search-modes) for more information.
The LDAP attribute that is used by Authelia to determine the group name.
### permit_referrals ### permit_referrals
@ -313,6 +284,71 @@ It's __strongly recommended__ this is a
[Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more [Random Alphanumeric String](../../reference/guides/generating-secure-values.md#generating-a-random-alphanumeric-string) with 64 or more
characters and the user password is changed to this value. characters and the user password is changed to this value.
### attributes
The following options configure The directory server attribute mappings.
#### distinguished_name
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically not required however it is required when using the group search mode
`memberof` replacement `{memberof:dn}`.*
The directory server attribute which contains the distinguished name, primarily used to perform filtered searches. There
is a clear distinction between the actual distinguished name and a distinguished name attribute, all directories have
distinguished names for objects, but not all have an attribute representing this that can be searched on.
The only known support at this time is with Active Directory.
#### username
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute that maps to the username in *Authelia*. This must contain the `{username_attribute}` [placeholder].
#### display_name
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute to retrieve which is shown on the Web UI to the user when they log in.
#### mail
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute to retrieve which contains the users email addresses. This is important for the device
registration and password reset processes. The user must have an email address in order for Authelia to perform
identity verification when a user attempts to reset their password or register a second factor device.
#### member_of
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute which contains the groups a user is a member of. This is currently only used for the
`memberof` group search mode.
#### group_name
{{< confkey type="string" required="situational" >}}
*__Note:__ This option is technically required however the [implementation](#implementation) option can implicitly set a
default negating this requirement. Refer to the [attribute defaults] for more information.*
The directory server attribute that is used by Authelia to determine the group name.
## Refresh Interval ## Refresh Interval
It's recommended you either use the default [refresh interval](introduction.md#refreshinterval) or configure this to It's recommended you either use the default [refresh interval](introduction.md#refreshinterval) or configure this to
@ -332,6 +368,8 @@ for your users.
- [LDAP Reference Guide](../../reference/guides/ldap.md) - [LDAP Reference Guide](../../reference/guides/ldap.md)
[username attribute]: #usernameattribute [username attribute]: #username
[TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx [TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx
[RFC2307]: https://datatracker.ietf.org/doc/html/rfc2307 [RFC2307]: https://datatracker.ietf.org/doc/html/rfc2307
[attribute defaults]: ../../reference/guides/ldap.md#attribute-defaults
[placeholder]: ../../reference/guides/ldap.md#users-filter-replacements

View File

@ -92,35 +92,35 @@ Use this [Standalone Example](#standalone-example) if you want to use
version: "3.8" version: "3.8"
secrets: secrets:
JWT_SECRET: JWT_SECRET:
file: ${PWD}/data/authelia/secrets/JWT_SECRET file: '${PWD}/data/authelia/secrets/JWT_SECRET'
SESSION_SECRET: SESSION_SECRET:
file: ${PWD}/data/authelia/secrets/SESSION_SECRET file: '${PWD}/data/authelia/secrets/SESSION_SECRET'
STORAGE_PASSWORD: STORAGE_PASSWORD:
file: ${PWD}/data/authelia/secrets/STORAGE_PASSWORD file: '${PWD}/data/authelia/secrets/STORAGE_PASSWORD'
STORAGE_ENCRYPTION_KEY: STORAGE_ENCRYPTION_KEY:
file: ${PWD}/data/authelia/secrets/STORAGE_ENCRYPTION_KEY file: '${PWD}/data/authelia/secrets/STORAGE_ENCRYPTION_KEY'
services: services:
authelia: authelia:
container_name: authelia container_name: 'authelia'
image: docker.io/authelia/authelia:latest image: 'docker.io/authelia/authelia:latest'
restart: unless-stopped restart: 'unless-stopped'
networks: networks:
net: net:
aliases: [] aliases: []
expose: expose:
- 9091 - 9091
secrets: [JWT_SECRET, SESSION_SECRET, STORAGE_PASSWORD, STORAGE_ENCRYPTION_KEY] secrets: ['JWT_SECRET', 'SESSION_SECRET', 'STORAGE_PASSWORD', 'STORAGE_ENCRYPTION_KEY']
environment: environment:
AUTHELIA_JWT_SECRET_FILE: /run/secrets/JWT_SECRET AUTHELIA_JWT_SECRET_FILE: '/run/secrets/JWT_SECRET'
AUTHELIA_SESSION_SECRET_FILE: /run/secrets/SESSION_SECRET AUTHELIA_SESSION_SECRET_FILE: '/run/secrets/SESSION_SECRET'
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: /run/secrets/STORAGE_PASSWORD AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: '/run/secrets/STORAGE_PASSWORD'
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /run/secrets/STORAGE_ENCRYPTION_KEY AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: '/run/secrets/STORAGE_ENCRYPTION_KEY'
volumes: volumes:
- ${PWD}/data/authelia/config:/config - '${PWD}/data/authelia/config:/config'
networks: networks:
net: net:
external: true external: true
name: net name: 'net'
... ...
``` ```
{{< /details >}} {{< /details >}}
@ -136,26 +136,26 @@ Use this [Standalone Example](#standalone-example) if you want to use a standard
version: "3.8" version: "3.8"
services: services:
authelia: authelia:
container_name: authelia container_name: 'authelia'
image: docker.io/authelia/authelia:latest image: 'docker.io/authelia/authelia:latest'
restart: unless-stopped restart: 'unless-stopped'
networks: networks:
net: net:
aliases: [] aliases: []
expose: expose:
- 9091 - 9091
environment: environment:
AUTHELIA_JWT_SECRET_FILE: /secrets/JWT_SECRET AUTHELIA_JWT_SECRET_FILE: '/secrets/JWT_SECRET'
AUTHELIA_SESSION_SECRET_FILE: /secrets/SESSION_SECRET AUTHELIA_SESSION_SECRET_FILE: '/secrets/SESSION_SECRET'
AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: /secrets/STORAGE_PASSWORD AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE: '/secrets/STORAGE_PASSWORD'
AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: /secrets/STORAGE_ENCRYPTION_KEY AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE: '/secrets/STORAGE_ENCRYPTION_KEY'
volumes: volumes:
- ${PWD}/data/authelia/config:/config - '${PWD}/data/authelia/config:/config'
- ${PWD}/data/authelia/secrets:/secrets - '${PWD}/data/authelia/secrets:/secrets'
networks: networks:
net: net:
external: true external: true
name: net name: 'net'
``` ```
... ...
{{< /details >}} {{< /details >}}

View File

@ -55,14 +55,16 @@ In your Authelia configuration you will need to enter and update the following v
base_dn: DC=example,DC=com base_dn: DC=example,DC=com
additional_users_dn: OU=users additional_users_dn: OU=users
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
username_attribute: uid
mail_attribute: mail
display_name_attribute: displayName
additional_groups_dn: OU=groups additional_groups_dn: OU=groups
groups_filter: (&(member=UID={input},OU=users,DC=example,DC=com)(objectClass=groupOfNames)) groups_filter: (&(member=UID={input},OU=users,DC=example,DC=com)(objectClass=groupOfNames))
group_name_attribute: cn
user: UID=authelia,OU=service accounts,DC=example,DC=com user: UID=authelia,OU=service accounts,DC=example,DC=com
password: "SUPER_COMPLEX_PASSWORD" password: "SUPER_COMPLEX_PASSWORD"
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
``` ```
Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object. Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
@ -100,16 +102,18 @@ In your Authelia configuration you will need to enter and update the following v
skip_verify: true skip_verify: true
minimum_version: TLS1.2 minimum_version: TLS1.2
base_dn: dc=example,DC=com base_dn: dc=example,DC=com
username_attribute: uid
additional_users_dn: CN=users,CN=accounts additional_users_dn: CN=users,CN=accounts
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))
additional_groups_dn: OU=groups additional_groups_dn: OU=groups
groups_filter: (&(member=UID={input},CN=users,CN=accounts,DC=example,DC=com)(objectClass=groupOfNames)) groups_filter: (&(member=UID={input},CN=users,CN=accounts,DC=example,DC=com)(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
user: UID=authelia,CN=users,CN=accounts,DC=example,DC=com user: UID=authelia,CN=users,CN=accounts,DC=example,DC=com
password: "SUPER_COMPLEX_PASSWORD" password: "SUPER_COMPLEX_PASSWORD"
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
``` ```
Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with Following this, restart Authelia, and you should be able to begin using LDAP integration for your user logins, with
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object. Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.
@ -139,19 +143,21 @@ ldap:
timeout: 5s timeout: 5s
start_tls: false start_tls: false
base_dn: dc=example,DC=com base_dn: dc=example,DC=com
username_attribute: uid
additional_users_dn: OU=people additional_users_dn: OU=people
# 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))
additional_groups_dn: OU=groups additional_groups_dn: OU=groups
groups_filter: (member={dn}) groups_filter: (member={dn})
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
# The username and password of the admin or service user. # The username and password of the admin or service user.
user: UID=authelia,OU=people,DC=example,DC=com user: UID=authelia,OU=people,DC=example,DC=com
password: "SUPER_COMPLEX_PASSWORD" password: "SUPER_COMPLEX_PASSWORD"
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
``` ```
Following this, restart Authelia, and you should be able to begin using lldap integration for your user logins, with Following this, restart Authelia, and you should be able to begin using lldap integration for your user logins, with
Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object. Authelia taking the email attribute for users straight from the 'mail' attribute within the LDAP object.

View File

@ -75,6 +75,32 @@ The following implementations exist:
[GLAuth]: https://glauth.github.io/ [GLAuth]: https://glauth.github.io/
[RFC2307bis]: https://datatracker.ietf.org/doc/html/draft-howard-rfc2307bis-02 [RFC2307bis]: https://datatracker.ietf.org/doc/html/draft-howard-rfc2307bis-02
### Group Search Modes
There are currently two group search modes that exist.
#### Search Mode: filter
The `filter` search mode is the default search mode. Generally this is recommended.
#### Search Mode: memberof
The `memberof` search mode is a special search mode. Generally this is discouraged and is currently experimental.
Some systems provide a `memberOf` attribute which may include additional groups that the user is a member of. This
search mode allows using this attribute as a method to determine their groups. How it works is the search is performed
against the base with the subtree scope and the groups filter must include one of the `{memberof:*}` replacements, and
the distinguished names of the results from the search are compared (case-insensitive) against the users `memberOf`
attribute to determine if they are members.
This means:
1. The groups still must be in the search base that you have configured.
2. The `memberOf` attribute *__MUST__* include the distinguished name of the group.
3. If the `{memberof:dn}` replacement is used:
1. The distinguished name *__MUST__* be searchable by your directory server.
3. The first relative distinguished name of the distinguished name *__MUST__* be search
### Filter replacements ### Filter replacements
Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP
@ -85,14 +111,21 @@ is ever established. In addition to this, during the startup phase we purposeful
phase replacements exist so we only have to check if the replacement is necessary once, and we don't needlessly perform phase replacements exist so we only have to check if the replacement is necessary once, and we don't needlessly perform
every possible replacement on every search regardless of if it's needed or not. every possible replacement on every search regardless of if it's needed or not.
#### Users filter replacements #### General filter replacements
| Placeholder | Phase | Replacement | | Placeholder | Phase | Replacement |
|:------------------------:|:-------:|:----------------------------------------------------------------------------------------------------------------:| |:------------------------------:|:-------:|:-------------------------------------------:|
| {distinguished_name_attribute} | startup | The configured distinguished name attribute |
| {username_attribute} | startup | The configured username attribute | | {username_attribute} | startup | The configured username attribute |
| {mail_attribute} | startup | The configured mail attribute | | {mail_attribute} | startup | The configured mail attribute |
| {display_name_attribute} | startup | The configured display name attribute | | {display_name_attribute} | startup | The configured display name attribute |
| {member_of_attribute} | startup | The configured member of attribute |
| {input} | search | The input into the username field | | {input} | search | The input into the username field |
#### Users filter replacements
| Placeholder | Phase | Replacement |
|:------------------------------:|:-------:|:----------------------------------------------------------------------------------------------------------------:|
| {date-time:generalized} | search | The current UTC time formatted as a LDAP generalized time in the format of `20060102150405.0Z` | | {date-time:generalized} | search | The current UTC time formatted as a LDAP generalized time in the format of `20060102150405.0Z` |
| {date-time:unix} | search | The current time formatted as a Unix epoch | | {date-time:unix} | search | The current time formatted as a Unix epoch |
| {date-time:microsoft-nt} | search | The current time formatted as a Microsoft NT epoch which is used by some Microsoft [Active Directory] attributes | | {date-time:microsoft-nt} | search | The current time formatted as a Microsoft NT epoch which is used by some Microsoft [Active Directory] attributes |
@ -100,10 +133,43 @@ every possible replacement on every search regardless of if it's needed or not.
#### Groups filter replacements #### Groups filter replacements
| Placeholder | Phase | Replacement | | Placeholder | Phase | Replacement |
|:-----------:|:------:|:-------------------------------------------------------------------------:| |:--------------:|:------:|:----------------------------------------------------------------------------------------------------------------------------------------------------:|
| {input} | search | The input into the username field |
| {username} | search | The username from the profile lookup obtained from the username attribute | | {username} | search | The username from the profile lookup obtained from the username attribute |
| {dn} | search | The distinguished name from the profile lookup | | {dn} | search | The distinguished name from the profile lookup |
| {memberof:dn} | search | See the detailed section below |
| {memberof:rdn} | search | Only allowed with the `memberof` search method and contains the first relative distinguished name of every `memberOf` entry a use has in parenthesis |
##### memberof:dn
Requirements:
1. Must be using the `memberof` search mode.
2. Must have the distinguished name attribute configured in Authelia.
3. Directory server must support searching by the distinguished name attribute (many directory services *__DO NOT__*
have a distinguished name attribute).
##### memberof:rdn
Requirements:
1. Must be using the `memberof` search mode.
2. Directory server must support searching by the first relative distinguished name as an attribute.
Splits every `memberOf` value to obtain th e first relative distinguished name and joins all of those after surrounding
them in parenthesis. This makes the general suggested filter pattern for this particular replacement
`(|{memberof:rdn})`. The format of this value is as follows:
```text
(<RDN>)
```
For example if the user has the following distinguished names in their object:
- CN=abc,OU=groups,DC=example,DC=com
- CN=xyz,OU=groups,DC=example,DC=com
The value will be replaced with `(CN=abc)(CN=xyz)` which using the suggested pattern for the filter becomes
`(|(CN=abc)(CN=xyz))` which will then return any user that as a `CN` of `abc` or `xyz`.
### Defaults ### Defaults
@ -122,14 +188,14 @@ The following set defaults for the `additional_users_dn` and `additional_groups_
This table describes the attribute defaults for each implementation. i.e. the username_attribute is described by the This table describes the attribute defaults for each implementation. i.e. the username_attribute is described by the
Username column. Username column.
| Implementation | Username | Display Name | Mail | Group Name | | Implementation | Username | Display Name | Mail | Group Name | Distinguished Name | Member Of |
|:---------------:|:--------------:|:------------:|:----:|:----------:| |:---------------:|:--------------:|:------------:|:----:|:----------:|:------------------:|:---------:|
| custom | N/A | displayName | mail | cn | | custom | N/A | displayName | mail | cn | N/A | N/A |
| activedirectory | sAMAccountName | displayName | mail | cn | | activedirectory | sAMAccountName | displayName | mail | cn | distinguishedName | memberOf |
| rfc2307bis | uid | displayName | mail | cn | | rfc2307bis | uid | displayName | mail | cn | N/A | memberOf |
| freeipa | uid | displayName | mail | cn | | freeipa | uid | displayName | mail | cn | N/A | memberOf |
| lldap | uid | cn | mail | cn | | lldap | uid | cn | mail | cn | N/A | memberOf |
| glauth | cn | description | mail | cn | | glauth | cn | description | mail | cn | N/A | memberOf |
#### Filter defaults #### Filter defaults
@ -146,8 +212,8 @@ the following conditions:
- Their password is expired: - Their password is expired:
- The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter. - The [Active Directory] implementation achieves this via the `(!(pwdLastSet=0))` filter.
- The [FreeIPA] implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter. - The [FreeIPA] implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter.
- The [RFC2307bis] implementation achieves this via the `(!(pwdReset=TRUE))` filter.
- The following implementations have no suitable attribute for this as far as we're aware: - The following implementations have no suitable attribute for this as far as we're aware:
- [RFC2307bis]
- [GLAuth] - [GLAuth]
- [lldap] - [lldap]
- Their account is expired: - Their account is expired:
@ -162,7 +228,7 @@ the following conditions:
|:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:| |:---------------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------------------------------:|
| custom | N/A | N/A | | custom | N/A | N/A |
| activedirectory | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(&#124;(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))) | (&(member={dn})(&#124;(sAMAccountType=268435456)(sAMAccountType=536870912))) | | activedirectory | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(&#124;(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt}))) | (&(member={dn})(&#124;(sAMAccountType=268435456)(sAMAccountType=536870912))) |
| rfc2307bis | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(&#124;(objectClass=inetOrgPerson)(objectClass=organizationalPerson))) | (&(&#124;(member={dn})(uniqueMember={dn}))(&#124;(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))) | | rfc2307bis | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(&#124;(objectClass=inetOrgPerson)(objectClass=organizationalPerson))(!(pwdReset=TRUE))) | (&(&#124;(member={dn})(uniqueMember={dn}))(&#124;(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))) |
| freeipa | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(&#124;(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) | | freeipa | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(&#124;(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))) | (&(member={dn})(objectClass=groupOfNames)) |
| lldap | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) | | lldap | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)) | (&(member={dn})(objectClass=groupOfNames)) |
| glauth | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) | | glauth | (&(&#124;({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive))) | (&(uniqueMember={dn})(objectClass=posixGroup)) |

File diff suppressed because one or more lines are too long

View File

@ -62,10 +62,17 @@ const (
const ( const (
ldapPlaceholderInput = "{input}" ldapPlaceholderInput = "{input}"
ldapPlaceholderDistinguishedName = "{dn}" ldapPlaceholderDistinguishedName = "{dn}"
ldapPlaceholderMemberOfDistinguishedName = "{memberof:dn}"
ldapPlaceholderMemberOfRelativeDistinguishedName = "{memberof:rdn}"
ldapPlaceholderUsername = "{username}" ldapPlaceholderUsername = "{username}"
ldapPlaceholderDateTimeGeneralized = "{date-time:generalized}" ldapPlaceholderDateTimeGeneralized = "{date-time:generalized}"
ldapPlaceholderDateTimeMicrosoftNTTimeEpoch = "{date-time:microsoft-nt}" ldapPlaceholderDateTimeMicrosoftNTTimeEpoch = "{date-time:microsoft-nt}"
ldapPlaceholderDateTimeUnixEpoch = "{date-time:unix}" ldapPlaceholderDateTimeUnixEpoch = "{date-time:unix}"
ldapPlaceholderDistinguishedNameAttribute = "{distinguished_name_attribute}"
ldapPlaceholderUsernameAttribute = "{username_attribute}"
ldapPlaceholderDisplayNameAttribute = "{display_name_attribute}"
ldapPlaceholderMailAttribute = "{mail_attribute}"
ldapPlaceholderMemberOfAttribute = "{member_of_attribute}"
) )
const ( const (

View File

@ -3,7 +3,7 @@ package authentication
// This file is used to generate mocks. You can generate all mocks using the // This file is used to generate mocks. You can generate all mocks using the
// command `go generate github.com/authelia/authelia/v4/internal/authentication`. // command `go generate github.com/authelia/authelia/v4/internal/authentication`.
//go:generate mockgen -package authentication -destination ldap_client_mock.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient //go:generate mockgen -package authentication -destination ldap_client_mock_test.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient
//go:generate mockgen -package authentication -destination ldap_client_factory_mock.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory //go:generate mockgen -package authentication -destination ldap_client_factory_mock_test.go -mock_names LDAPClientFactory=MockLDAPClientFactory github.com/authelia/authelia/v4/internal/authentication LDAPClientFactory
//go:generate mockgen -package authentication -destination file_user_provider_database_mock_test.go -mock_names FileUserDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserDatabase //go:generate mockgen -package authentication -destination file_user_provider_database_mock_test.go -mock_names FileUserDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserDatabase
//go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash //go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash

View File

@ -0,0 +1,17 @@
package authentication
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestControlMsftServerPolicyHints(t *testing.T) {
ct := &controlMsftServerPolicyHints{
oid: ldapOIDControlMsftServerPolicyHints,
}
assert.Equal(t, ldapOIDControlMsftServerPolicyHints, ct.GetControlType())
assert.Equal(t, "Enforce the password history length constraint (MS-SAMR section 3.1.1.7.1) during password set: 1.2.840.113556.1.4.2239", ct.String())
assert.NotNil(t, ct.Encode())
}

View File

@ -8,7 +8,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/go-ldap/ldap/v3" ldap "github.com/go-ldap/ldap/v3"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/configuration/schema"
@ -45,6 +45,8 @@ type LDAPUserProvider struct {
groupsFilterReplacementInput bool groupsFilterReplacementInput bool
groupsFilterReplacementUsername bool groupsFilterReplacementUsername bool
groupsFilterReplacementDN bool groupsFilterReplacementDN bool
groupsFilterReplacementsMemberOfDN bool
groupsFilterReplacementsMemberOfRDN bool
} }
// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory. // NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory.
@ -86,6 +88,7 @@ func NewLDAPUserProviderWithFactory(config schema.LDAPAuthenticationBackend, dis
provider.parseDynamicUsersConfiguration() provider.parseDynamicUsersConfiguration()
provider.parseDynamicGroupsConfiguration() provider.parseDynamicGroupsConfiguration()
provider.parseDynamicConfiguration()
return provider return provider
} }
@ -134,38 +137,11 @@ func (p *LDAPUserProvider) GetDetails(username string) (details *UserDetails, er
} }
var ( var (
request *ldap.SearchRequest groups []string
result *ldap.SearchResult
) )
// Search for the users groups. if groups, err = p.getUserGroups(client, username, profile); err != nil {
request = ldap.NewSearchRequest( return nil, err
p.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
0, 0, false, p.resolveGroupsFilter(username, profile), p.groupsAttributes, nil,
)
p.log.
WithField("base_dn", request.BaseDN).
WithField("filter", request.Filter).
WithField("attr", request.Attributes).
WithField("scope", request.Scope).
WithField("deref", request.DerefAliases).
Trace("Performing group search")
if result, err = p.search(client, request); err != nil {
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
}
groups := make([]string, 0)
for _, res := range result.Entries {
if len(res.Attributes) == 0 {
p.log.Warningf("No groups retrieved from LDAP for user %s", username)
break
}
// Append all values of the document. Normally there should be only one per document.
groups = append(groups, res.Attributes[0].Values...)
} }
return &UserDetails{ return &UserDetails{
@ -275,14 +251,12 @@ func (p *LDAPUserProvider) search(client LDAPClient, request *ldap.SearchRequest
} else { } else {
result.Referrals = append(result.Referrals, referral) result.Referrals = append(result.Referrals, referral)
} }
} else {
return nil, err
} }
} }
if !p.config.PermitReferrals || len(result.Referrals) == 0 { if !p.config.PermitReferrals || len(result.Referrals) == 0 {
if err != nil {
return nil, err
}
return result, nil return result, nil
} }
@ -357,6 +331,11 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p
return nil, fmt.Errorf("there were %d users found when searching for '%s' but there should only be 1", len(result.Entries), username) return nil, fmt.Errorf("there were %d users found when searching for '%s' but there should only be 1", len(result.Entries), username)
} }
return p.getUserProfileResultToProfile(username, result)
}
//nolint:gocyclo // Not overly complex.
func (p *LDAPUserProvider) getUserProfileResultToProfile(username string, result *ldap.SearchResult) (profile *ldapUserProfile, err error) {
userProfile := ldapUserProfile{ userProfile := ldapUserProfile{
DN: result.Entries[0].DN, DN: result.Entries[0].DN,
} }
@ -364,35 +343,50 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p
for _, attr := range result.Entries[0].Attributes { for _, attr := range result.Entries[0].Attributes {
attrs := len(attr.Values) attrs := len(attr.Values)
if attr.Name == p.config.UsernameAttribute { switch attr.Name {
case p.config.Attributes.Username:
switch attrs { switch attrs {
case 1: case 1:
userProfile.Username = attr.Values[0] userProfile.Username = attr.Values[0]
case 0:
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'", if attr.Name == p.config.Attributes.DisplayName && userProfile.DisplayName == "" {
username, p.config.UsernameAttribute) userProfile.DisplayName = attr.Values[0]
default:
return nil, fmt.Errorf("user '%s' has %d values for for attribute '%s' but the attribute must be a single value attribute",
username, attrs, p.config.UsernameAttribute)
}
} }
if attr.Name == p.config.Attributes.Mail && len(userProfile.Emails) == 0 {
userProfile.Emails = []string{attr.Values[0]}
}
case 0:
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'",
username, p.config.Attributes.Username)
default:
return nil, fmt.Errorf("user '%s' has %d values for for attribute '%s' but the attribute must be a single value attribute",
username, attrs, p.config.Attributes.Username)
}
case p.config.Attributes.Mail:
if attrs == 0 { if attrs == 0 {
continue continue
} }
if attr.Name == p.config.MailAttribute {
userProfile.Emails = attr.Values userProfile.Emails = attr.Values
case p.config.Attributes.DisplayName:
if attrs == 0 {
continue
} }
if attr.Name == p.config.DisplayNameAttribute {
userProfile.DisplayName = attr.Values[0] userProfile.DisplayName = attr.Values[0]
case p.config.Attributes.MemberOf:
if attrs == 0 {
continue
}
userProfile.MemberOf = attr.Values
} }
} }
if userProfile.Username == "" { if userProfile.Username == "" {
return nil, fmt.Errorf("user '%s' must have value for attribute '%s'", return nil, fmt.Errorf("user '%s' must have value for attribute '%s'",
username, p.config.UsernameAttribute) username, p.config.Attributes.Username)
} }
if userProfile.DN == "" { if userProfile.DN == "" {
@ -402,6 +396,118 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p
return &userProfile, nil return &userProfile, nil
} }
func (p *LDAPUserProvider) getUserGroups(client LDAPClient, username string, profile *ldapUserProfile) (groups []string, err error) {
request := ldap.NewSearchRequest(
p.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
0, 0, false, p.resolveGroupsFilter(username, profile), p.groupsAttributes, nil,
)
p.log.
WithField("base_dn", request.BaseDN).
WithField("filter", request.Filter).
WithField("attributes", request.Attributes).
WithField("scope", request.Scope).
WithField("deref", request.DerefAliases).
WithField("mode", p.config.GroupSearchMode).
Trace("Performing group search")
switch p.config.GroupSearchMode {
case "", "filter":
return p.getUserGroupsRequestFilter(client, username, profile, request)
case "memberof":
return p.getUserGroupsRequestMemberOf(client, username, profile, request)
default:
return nil, fmt.Errorf("could not perform group search with mode '%s' as it's unknown", p.config.GroupSearchMode)
}
}
func (p *LDAPUserProvider) getUserGroupsRequestFilter(client LDAPClient, username string, _ *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) {
var result *ldap.SearchResult
if result, err = p.search(client, request); err != nil {
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
}
for _, entry := range result.Entries {
if group := p.getUserGroupFromEntry(entry); len(group) != 0 {
groups = append(groups, group)
}
}
return groups, nil
}
func (p *LDAPUserProvider) getUserGroupsRequestMemberOf(client LDAPClient, username string, profile *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) {
var result *ldap.SearchResult
if result, err = p.search(client, request); err != nil {
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", username, err)
}
for _, entry := range result.Entries {
if len(entry.Attributes) == 0 {
p.log.
WithField("dn", entry.DN).
WithField("attributes", request.Attributes).
WithField("mode", "memberof").
Trace("Skipping Group as the server did not return any requested attributes")
continue
}
if !utils.IsStringInSliceFold(entry.DN, profile.MemberOf) {
p.log.
WithField("dn", entry.DN).
WithField("mode", "memberof").
Trace("Skipping Group as it doesn't match the users memberof entries")
continue
}
if group := p.getUserGroupFromEntry(entry); len(group) != 0 {
groups = append(groups, group)
}
}
return groups, nil
}
func (p *LDAPUserProvider) getUserGroupFromEntry(entry *ldap.Entry) string {
attributes:
for _, attr := range entry.Attributes {
switch attr.Name {
case p.config.Attributes.GroupName:
switch len(attr.Values) {
case 0:
p.log.
WithField("dn", entry.DN).
WithField("attribute", attr.Name).
Trace("Group skipped as the server returned a null attribute")
case 1:
switch len(attr.Values[0]) {
case 0:
p.log.
WithField("dn", entry.DN).
WithField("attribute", attr.Name).
Trace("Skipping group as the configured group name attribute had no value")
default:
return attr.Values[0]
}
default:
p.log.
WithField("dn", entry.DN).
WithField("attribute", attr.Name).
Trace("Group skipped as the server returned a multi-valued attribute but it should be a single-valued attribute")
}
break attributes
}
}
return ""
}
func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) { func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) {
filter = p.config.UsersFilter filter = p.config.UsersFilter
@ -445,6 +551,27 @@ func (p *LDAPUserProvider) resolveGroupsFilter(input string, profile *ldapUserPr
} }
} }
if p.groupsFilterReplacementsMemberOfDN {
sep := fmt.Sprintf(")(%s=", p.config.Attributes.DistinguishedName)
values := make([]string, len(profile.MemberOf))
for i, memberof := range profile.MemberOf {
values[i] = ldap.EscapeFilter(memberof)
}
filter = strings.ReplaceAll(filter, ldapPlaceholderMemberOfDistinguishedName, fmt.Sprintf("(%s=%s)", p.config.Attributes.DistinguishedName, strings.Join(values, sep)))
}
if p.groupsFilterReplacementsMemberOfRDN {
values := make([]string, len(profile.MemberOf))
for i, memberof := range profile.MemberOf {
values[i] = ldap.EscapeFilter(strings.SplitN(memberof, ",", 2)[0])
}
filter = strings.ReplaceAll(filter, ldapPlaceholderMemberOfRelativeDistinguishedName, fmt.Sprintf("(%s)", strings.Join(values, ")(")))
}
p.log.Tracef("Computed groups filter is %s", filter) p.log.Tracef("Computed groups filter is %s", filter)
return filter return filter

View File

@ -37,11 +37,6 @@ func (p *LDAPUserProvider) StartupCheck() (err error) {
"LDAP Server.") "LDAP Server.")
} }
if !p.features.Extensions.TLS && p.config.StartTLS {
p.log.Info("Your LDAP Server does not appear to support TLS but you enabled StartTLS which may result " +
"in an error.")
}
return nil return nil
} }
@ -90,22 +85,24 @@ func (p *LDAPUserProvider) getServerSupportedFeatures(client LDAPClient) (featur
} }
func (p *LDAPUserProvider) parseDynamicUsersConfiguration() { func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{username_attribute}", p.config.UsernameAttribute) p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName)
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{mail_attribute}", p.config.MailAttribute) p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username)
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, "{display_name_attribute}", p.config.DisplayNameAttribute) p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName)
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail)
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf)
p.log.Tracef("Dynamically generated users filter is %s", p.config.UsersFilter) p.log.Tracef("Dynamically generated users filter is %s", p.config.UsersFilter)
if !utils.IsStringInSlice(p.config.UsernameAttribute, p.usersAttributes) { if len(p.config.Attributes.Username) != 0 && !utils.IsStringInSlice(p.config.Attributes.Username, p.usersAttributes) {
p.usersAttributes = append(p.usersAttributes, p.config.UsernameAttribute) p.usersAttributes = append(p.usersAttributes, p.config.Attributes.Username)
} }
if !utils.IsStringInSlice(p.config.MailAttribute, p.usersAttributes) { if len(p.config.Attributes.Mail) != 0 && !utils.IsStringInSlice(p.config.Attributes.Mail, p.usersAttributes) {
p.usersAttributes = append(p.usersAttributes, p.config.MailAttribute) p.usersAttributes = append(p.usersAttributes, p.config.Attributes.Mail)
} }
if !utils.IsStringInSlice(p.config.DisplayNameAttribute, p.usersAttributes) { if len(p.config.Attributes.DisplayName) != 0 && !utils.IsStringInSlice(p.config.Attributes.DisplayName, p.usersAttributes) {
p.usersAttributes = append(p.usersAttributes, p.config.DisplayNameAttribute) p.usersAttributes = append(p.usersAttributes, p.config.Attributes.DisplayName)
} }
if p.config.AdditionalUsersDN != "" { if p.config.AdditionalUsersDN != "" {
@ -137,8 +134,14 @@ func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
} }
func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() { func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
p.groupsAttributes = []string{ p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName)
p.config.GroupNameAttribute, p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username)
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName)
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail)
p.config.GroupsFilter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf)
if len(p.config.Attributes.GroupName) != 0 && !utils.IsStringInSlice(p.config.Attributes.GroupName, p.groupsAttributes) {
p.groupsAttributes = append(p.groupsAttributes, p.config.Attributes.GroupName)
} }
if p.config.AdditionalGroupsDN != "" { if p.config.AdditionalGroupsDN != "" {
@ -161,5 +164,25 @@ func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
p.groupsFilterReplacementDN = true p.groupsFilterReplacementDN = true
} }
if strings.Contains(p.config.GroupsFilter, ldapPlaceholderMemberOfDistinguishedName) {
p.groupsFilterReplacementsMemberOfDN = true
}
if strings.Contains(p.config.GroupsFilter, ldapPlaceholderMemberOfRelativeDistinguishedName) {
p.groupsFilterReplacementsMemberOfRDN = true
}
p.log.Tracef("Detected group filter replacements that need to be resolved per lookup are: input=%v, username=%v, dn=%v", p.groupsFilterReplacementInput, p.groupsFilterReplacementUsername, p.groupsFilterReplacementDN) p.log.Tracef("Detected group filter replacements that need to be resolved per lookup are: input=%v, username=%v, dn=%v", p.groupsFilterReplacementInput, p.groupsFilterReplacementUsername, p.groupsFilterReplacementDN)
} }
func (p *LDAPUserProvider) parseDynamicConfiguration() {
if len(p.config.Attributes.MemberOf) != 0 {
if !utils.IsStringInSlice(p.config.Attributes.MemberOf, p.usersAttributes) {
p.usersAttributes = append(p.usersAttributes, p.config.Attributes.MemberOf)
}
if !utils.IsStringInSlice(p.config.Attributes.MemberOf, p.groupsAttributes) {
p.groupsAttributes = append(p.groupsAttributes, p.config.Attributes.MemberOf)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ import (
"strings" "strings"
ber "github.com/go-asn1-ber/asn1-ber" ber "github.com/go-asn1-ber/asn1-ber"
"github.com/go-ldap/ldap/v3" ldap "github.com/go-ldap/ldap/v3"
) )
func ldapEntriesContainsEntry(needle *ldap.Entry, haystack []*ldap.Entry) bool { func ldapEntriesContainsEntry(needle *ldap.Entry, haystack []*ldap.Entry) bool {
@ -67,12 +67,12 @@ func ldapEscape(inputUsername string) string {
} }
func ldapGetReferral(err error) (referral string, ok bool) { func ldapGetReferral(err error) (referral string, ok bool) {
if !ldap.IsErrorWithCode(err, ldap.LDAPResultReferral) { switch e := err.(type) {
case *ldap.Error:
if e.ResultCode != ldap.LDAPResultReferral {
return "", false return "", false
} }
switch e := err.(type) {
case *ldap.Error:
if e.Packet == nil { if e.Packet == nil {
return "", false return "", false
} }

View File

@ -193,6 +193,12 @@ func TestLDAPGetReferral(t *testing.T) {
expectedReferral: "", expectedReferral: "",
expectedOK: false, expectedOK: false,
}, },
{
description: "ShouldNotGetInvalidErrType",
have: errors.New("not an err"),
expectedReferral: "",
expectedOK: false,
},
} }
for _, tc := range testCases { for _, tc := range testCases {

View File

@ -82,6 +82,7 @@ type ldapUserProfile struct {
Emails []string Emails []string
DisplayName string DisplayName string
Username string Username string
MemberOf []string
} }
// LDAPSupportedFeatures represents features which a server may support which are implemented in code. // LDAPSupportedFeatures represents features which a server may support which are implemented in code.

View File

@ -315,7 +315,7 @@ authentication_backend:
## because it allows Authelia to offload the stateful operations ## because it allows Authelia to offload the stateful operations
## onto the LDAP service. ## onto the LDAP service.
# ldap: # ldap:
## The address of the LDAP server to connect to in the address common syntax. ## The address of the directory server to connect to in the address common syntax.
## Format: [<scheme>://]<hostname>[:<port>]. ## Format: [<scheme>://]<hostname>[:<port>].
## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`. ## Square brackets indicate optional portions of the format. Scheme must be 'ldap', 'ldaps', or 'ldapi`.
## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'. ## The default scheme is 'ldapi' if the address is an absolute path otherwise it's 'ldaps'.
@ -401,16 +401,6 @@ authentication_backend:
## See also: additional_users_dn, additional_groups_dn. ## See also: additional_users_dn, additional_groups_dn.
# 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 username in the session
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP usually
## uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and the
## configuration stored in database. Therefore only single value attributes are allowed and the value must never be
## changed once attributed to a user otherwise it would break the configuration 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
## a filter to perform alternative lookups and the attributes mentioned above (sAMAccountName and uid) to
## follow https://datatracker.ietf.org/doc/html/rfc2307.
# username_attribute: 'uid'
## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users. ## The additional_users_dn is prefixed to base_dn and delimited by a comma when searching for users.
## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users. ## i.e. with this set to OU=Users and base_dn set to DC=a,DC=com; OU=Users,DC=a,DC=com is searched for users.
# additional_users_dn: 'ou=users' # additional_users_dn: 'ou=users'
@ -441,15 +431,9 @@ authentication_backend:
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames)) ## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
# groups_filter: '(&(member={dn})(objectClass=groupOfNames))' # groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
## The attribute holding the name of the group. ## The group search mode to use. Options are 'filter' or 'memberof'. It's essential to read the docs if you wish to
# group_name_attribute: 'cn' ## use 'memberof'. Also 'filter' is the best choice for most use cases.
# group_search_mode: 'filter'
## 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.
# 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'
## Follow referrals returned by the server. ## Follow referrals returned by the server.
## This is especially useful for environments where read-only servers exist. Only implemented for write operations. ## This is especially useful for environments where read-only servers exist. Only implemented for write operations.
@ -460,6 +444,37 @@ authentication_backend:
## Password can also be set using a secret: https://www.authelia.com/c/secrets ## Password can also be set using a secret: https://www.authelia.com/c/secrets
# password: 'password' # password: 'password'
## The attributes for users and objects from the directory server.
# attributes:
## The distinguished name attribute if your directory server supports it. Users should read the docs before
## configuring. Only used for the 'memberof' group search mode.
# distinguished_name: ''
## The attribute holding the username of the user. This attribute is used to populate the username in the session
## information. For your information, Microsoft Active Directory usually uses 'sAMAccountName' and OpenLDAP
## usually uses 'uid'. Beware that this attribute holds the unique identifiers for the users binding the user and
## the configuration stored in database; therefore only single value attributes are allowed and the value must
## never be changed once attributed to a user otherwise it would break the configuration 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 a filter to perform alternative lookups and the attributes mentioned above
## (sAMAccountName and uid) to follow https://datatracker.ietf.org/doc/html/rfc2307.
# username: 'uid'
## The attribute holding the display name of the user. This will be used to greet an authenticated user.
# display_name: 'displayName'
## 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 directory server is used.
# mail: 'mail'
## The attribute which provides distinguished names of groups an object is a member of.
## Only used for the 'memberof' group search mode.
# member_of: 'memberOf'
## The attribute holding the name of the group.
# group_name: 'cn'
## ##
## File (Authentication Provider) ## File (Authentication Provider)
## ##

View File

@ -294,4 +294,36 @@ var deprecations = map[string]Deprecation{
MapFunc: nil, MapFunc: nil,
ErrFunc: nil, ErrFunc: nil,
}, },
"authentication_backend.ldap.username_attribute": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "authentication_backend.ldap.username_attribute",
NewKey: "authentication_backend.ldap.attributes.username",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
"authentication_backend.ldap.mail_attribute": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "authentication_backend.ldap.mail_attribute",
NewKey: "authentication_backend.ldap.attributes.mail",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
"authentication_backend.ldap.display_name_attribute": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "authentication_backend.ldap.display_name_attribute",
NewKey: "authentication_backend.ldap.attributes.display_name",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
"authentication_backend.ldap.group_name_attribute": {
Version: model.SemanticVersion{Major: 4, Minor: 38},
Key: "authentication_backend.ldap.group_name_attribute",
NewKey: "authentication_backend.ldap.attributes.group_name",
AutoMap: true,
MapFunc: nil,
ErrFunc: nil,
},
} }

View File

@ -245,6 +245,28 @@ func TestShouldLoadURLList(t *testing.T) {
assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String()) assert.Equal(t, "https://example.com", config.IdentityProviders.OIDC.CORS.AllowedOrigins[1].String())
} }
func TestShouldDisableOIDCEntropy(t *testing.T) {
val := schema.NewStructValidator()
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc_disable_entropy.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)
assert.NoError(t, err)
validator.ValidateKeys(keys, DefaultEnvPrefix, val)
assert.Len(t, val.Errors(), 0)
assert.Len(t, val.Warnings(), 0)
assert.Equal(t, -1, config.IdentityProviders.OIDC.MinimumParameterEntropy)
validator.ValidateIdentityProviders(&config.IdentityProviders, val)
assert.Len(t, val.Errors(), 1)
require.Len(t, val.Warnings(), 2)
assert.EqualError(t, val.Warnings()[0], "identity_providers: oidc: option 'minimum_parameter_entropy' is disabled which is considered unsafe and insecure")
assert.Equal(t, -1, config.IdentityProviders.OIDC.MinimumParameterEntropy)
}
func TestShouldConfigureConsent(t *testing.T) { func TestShouldConfigureConsent(t *testing.T) {
val := schema.NewStructValidator() val := schema.NewStructValidator()
keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...) keys, config, err := Load(val, NewDefaultSources([]string{"./test_resources/config_oidc.yml"}, DefaultEnvPrefix, DefaultEnvDelimiter)...)

View File

@ -108,11 +108,9 @@ type LDAPAuthenticationBackend struct {
AdditionalGroupsDN string `koanf:"additional_groups_dn"` AdditionalGroupsDN string `koanf:"additional_groups_dn"`
GroupsFilter string `koanf:"groups_filter"` GroupsFilter string `koanf:"groups_filter"`
GroupSearchMode string `koanf:"group_search_mode"`
GroupNameAttribute string `koanf:"group_name_attribute"` Attributes LDAPAuthenticationAttributes `koanf:"attributes"`
UsernameAttribute string `koanf:"username_attribute"`
MailAttribute string `koanf:"mail_attribute"`
DisplayNameAttribute string `koanf:"display_name_attribute"`
PermitReferrals bool `koanf:"permit_referrals"` PermitReferrals bool `koanf:"permit_referrals"`
PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"` PermitUnauthenticatedBind bool `koanf:"permit_unauthenticated_bind"`
@ -122,6 +120,16 @@ type LDAPAuthenticationBackend struct {
Password string `koanf:"password"` Password string `koanf:"password"`
} }
// LDAPAuthenticationAttributes represents the configuration related to LDAP server attributes.
type LDAPAuthenticationAttributes struct {
DistinguishedName string `koanf:"distinguished_name"`
Username string `koanf:"username"`
DisplayName string `koanf:"display_name"`
Mail string `koanf:"mail"`
MemberOf string `koanf:"member_of"`
GroupName string `koanf:"group_name"`
}
// DefaultPasswordConfig represents the default configuration related to Argon2id hashing. // DefaultPasswordConfig represents the default configuration related to Argon2id hashing.
var DefaultPasswordConfig = Password{ var DefaultPasswordConfig = Password{
Algorithm: argon2, Algorithm: argon2,
@ -175,10 +183,13 @@ var DefaultCIPasswordConfig = Password{
// DefaultLDAPAuthenticationBackendConfigurationImplementationCustom represents the default LDAP config. // DefaultLDAPAuthenticationBackendConfigurationImplementationCustom represents the default LDAP config.
var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuthenticationBackend{
UsernameAttribute: ldapAttrUserID, GroupSearchMode: ldapGroupSearchModeFilter,
MailAttribute: ldapAttrMail, Attributes: LDAPAuthenticationAttributes{
DisplayNameAttribute: ldapAttrDisplayName, Username: ldapAttrUserID,
GroupNameAttribute: ldapAttrCommonName, DisplayName: ldapAttrDisplayName,
Mail: ldapAttrMail,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5, Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
@ -188,11 +199,16 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuth
// DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt})))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:microsoft-nt})))",
UsernameAttribute: "sAMAccountName",
MailAttribute: ldapAttrMail,
DisplayNameAttribute: ldapAttrDisplayName,
GroupsFilter: "(&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912)))", GroupsFilter: "(&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912)))",
GroupNameAttribute: ldapAttrCommonName, GroupSearchMode: ldapGroupSearchModeFilter,
Attributes: LDAPAuthenticationAttributes{
DistinguishedName: ldapAttrDistinguishedName,
Username: ldapAttrSAMAccountName,
DisplayName: ldapAttrDisplayName,
Mail: ldapAttrMail,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5, Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
@ -202,11 +218,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory =
// DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis represents the default LDAP config for the LDAPImplementationRFC2307bis Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis represents the default LDAP config for the LDAPImplementationRFC2307bis Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson)))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(|(objectClass=inetOrgPerson)(objectClass=organizationalPerson)))",
UsernameAttribute: ldapAttrUserID, GroupsFilter: "(&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers))(!(pwdReset=TRUE)))",
MailAttribute: ldapAttrMail, GroupSearchMode: ldapGroupSearchModeFilter,
DisplayNameAttribute: ldapAttrDisplayName, Attributes: LDAPAuthenticationAttributes{
GroupsFilter: "(&(|(member={dn})(uniqueMember={dn}))(|(objectClass=groupOfNames)(objectClass=groupOfUniqueNames)(objectClass=groupOfMembers)))", Username: ldapAttrUserID,
GroupNameAttribute: ldapAttrCommonName, DisplayName: ldapAttrDisplayName,
Mail: ldapAttrMail,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5, Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
@ -216,11 +236,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis = LDAP
// DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))",
UsernameAttribute: ldapAttrUserID,
MailAttribute: ldapAttrMail,
DisplayNameAttribute: ldapAttrDisplayName,
GroupsFilter: "(&(member={dn})(objectClass=groupOfNames))", GroupsFilter: "(&(member={dn})(objectClass=groupOfNames))",
GroupNameAttribute: ldapAttrCommonName, GroupSearchMode: ldapGroupSearchModeFilter,
Attributes: LDAPAuthenticationAttributes{
Username: ldapAttrUserID,
DisplayName: ldapAttrDisplayName,
Mail: ldapAttrMail,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5, Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
@ -232,11 +256,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP = LDAPAuthe
AdditionalUsersDN: "OU=people", AdditionalUsersDN: "OU=people",
AdditionalGroupsDN: "OU=groups", AdditionalGroupsDN: "OU=groups",
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))",
UsernameAttribute: ldapAttrUserID,
MailAttribute: ldapAttrMail,
DisplayNameAttribute: ldapAttrCommonName,
GroupsFilter: "(&(member={dn})(objectClass=groupOfUniqueNames))", GroupsFilter: "(&(member={dn})(objectClass=groupOfUniqueNames))",
GroupNameAttribute: ldapAttrCommonName, GroupSearchMode: ldapGroupSearchModeFilter,
Attributes: LDAPAuthenticationAttributes{
Username: ldapAttrUserID,
DisplayName: ldapAttrCommonName,
Mail: ldapAttrMail,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5, Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},
@ -246,11 +274,15 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP = LDAPAuthe
// DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth represents the default LDAP config for the LDAPImplementationGLAuth Implementation. // DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth represents the default LDAP config for the LDAPImplementationGLAuth Implementation.
var DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth = LDAPAuthenticationBackend{ var DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth = LDAPAuthenticationBackend{
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive)))", UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=posixAccount)(!(accountStatus=inactive)))",
UsernameAttribute: ldapAttrCommonName,
MailAttribute: ldapAttrMail,
DisplayNameAttribute: ldapAttrDescription,
GroupsFilter: "(&(uniqueMember={dn})(objectClass=posixGroup))", GroupsFilter: "(&(uniqueMember={dn})(objectClass=posixGroup))",
GroupNameAttribute: ldapAttrCommonName, GroupSearchMode: ldapGroupSearchModeFilter,
Attributes: LDAPAuthenticationAttributes{
Username: ldapAttrCommonName,
DisplayName: ldapAttrDescription,
Mail: ldapAttrMail,
MemberOf: ldapAttrMemberOf,
GroupName: ldapAttrCommonName,
},
Timeout: time.Second * 5, Timeout: time.Second * 5,
TLS: &TLSConfig{ TLS: &TLSConfig{
MinimumVersion: TLSVersion{tls.VersionTLS12}, MinimumVersion: TLSVersion{tls.VersionTLS12},

View File

@ -78,6 +78,14 @@ const (
LDAPImplementationGLAuth = "glauth" LDAPImplementationGLAuth = "glauth"
) )
const (
// LDAPGroupSearchModeFilter is the string for the filter group search mode.
LDAPGroupSearchModeFilter = "filter"
// LDAPGroupSearchModeMemberOf is the string for the memberOf group search mode.
LDAPGroupSearchModeMemberOf = "memberof"
)
// TOTP Algorithm. // TOTP Algorithm.
const ( const (
TOTPAlgorithmSHA1 = "SHA1" TOTPAlgorithmSHA1 = "SHA1"
@ -121,11 +129,18 @@ const (
) )
const ( const (
ldapGroupSearchModeFilter = "filter"
)
const (
ldapAttrDistinguishedName = "distinguishedName"
ldapAttrMail = "mail" ldapAttrMail = "mail"
ldapAttrUserID = "uid" ldapAttrUserID = "uid"
ldapAttrSAMAccountName = "sAMAccountName"
ldapAttrDisplayName = "displayName" ldapAttrDisplayName = "displayName"
ldapAttrDescription = "description" ldapAttrDescription = "description"
ldapAttrCommonName = "cn" ldapAttrCommonName = "cn"
ldapAttrMemberOf = "memberOf"
) )
// Address Schemes. // Address Schemes.

View File

@ -120,10 +120,13 @@ var Keys = []string{
"authentication_backend.ldap.users_filter", "authentication_backend.ldap.users_filter",
"authentication_backend.ldap.additional_groups_dn", "authentication_backend.ldap.additional_groups_dn",
"authentication_backend.ldap.groups_filter", "authentication_backend.ldap.groups_filter",
"authentication_backend.ldap.group_name_attribute", "authentication_backend.ldap.group_search_mode",
"authentication_backend.ldap.username_attribute", "authentication_backend.ldap.attributes.distinguished_name",
"authentication_backend.ldap.mail_attribute", "authentication_backend.ldap.attributes.username",
"authentication_backend.ldap.display_name_attribute", "authentication_backend.ldap.attributes.display_name",
"authentication_backend.ldap.attributes.mail",
"authentication_backend.ldap.attributes.member_of",
"authentication_backend.ldap.attributes.group_name",
"authentication_backend.ldap.permit_referrals", "authentication_backend.ldap.permit_referrals",
"authentication_backend.ldap.permit_unauthenticated_bind", "authentication_backend.ldap.permit_unauthenticated_bind",
"authentication_backend.ldap.permit_feature_detection_failure", "authentication_backend.ldap.permit_feature_detection_failure",

View File

@ -66,14 +66,15 @@ authentication_backend:
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw== 1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -47,14 +47,15 @@ authentication_backend:
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw== 1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
base_dn: dc=example,dc=com base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user)) users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
additional_groups_dn: ou=groups additional_groups_dn: ou=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames)) groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: deny default_policy: deny

View File

@ -66,14 +66,15 @@ authentication_backend:
1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw== 1YjCJ36UpTsLre2f8nOSLtNmRfDPtbOE2mkOoO9dD9UU0XZwnvn9xw==
-----END RSA PRIVATE KEY----- -----END RSA PRIVATE KEY-----
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -18,14 +18,16 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
mail: 'mail'
group_name: 'cn'
username: 'uid'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -19,14 +19,15 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -18,14 +18,15 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -18,13 +18,15 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn' attributes:
mail_attribute: 'mail' group_name: 'cn'
mail: 'mail'
username: 'uid'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
access_control: access_control:

View File

@ -18,14 +18,15 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -0,0 +1,137 @@
---
default_redirection_url: 'https://home.example.com:8080/'
server:
address: 'tcp://127.0.0.1:9091'
log:
level: 'debug'
totp:
issuer: 'authelia.com'
duo_api:
hostname: 'api-123456789.example.com'
integration_key: 'ABCDEF'
authentication_backend:
ldap:
address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com'
additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control:
default_policy: 'deny'
rules:
# Rules applied to everyone
- domain: 'public.example.com'
policy: 'bypass'
- domain: 'secure.example.com'
policy: 'one_factor'
# Network based rule, if not provided any network matches.
networks:
- '192.168.1.0/24'
- domain: 'secure.example.com'
policy: 'two_factor'
- domain: ['singlefactor.example.com', 'onefactor.example.com']
policy: 'one_factor'
# Rules applied to 'admins' group
- domain: 'mx2.mail.example.com'
subject: 'group:admins'
policy: 'deny'
- domain: '*.example.com'
subject: 'group:admins'
policy: 'two_factor'
# Rules applied to 'dev' group
- domain: 'dev.example.com'
resources:
- '^/groups/dev/.*$'
subject: 'group:dev'
policy: 'two_factor'
# Rules applied to user 'john'
- domain: 'dev.example.com'
resources:
- '^/users/john/.*$'
subject: 'user:john'
policy: 'two_factor'
# Rules applied to 'dev' group and user 'john'
- domain: 'dev.example.com'
resources:
- '^/deny-all.*$'
subject: ['group:dev', 'user:john']
policy: 'deny'
# Rules applied to user 'harry'
- domain: 'dev.example.com'
resources:
- '^/users/harry/.*$'
subject: 'user:harry'
policy: 'two_factor'
# Rules applied to user 'bob'
- domain: '*.mail.example.com'
subject: 'user:bob'
policy: 'two_factor'
- domain: 'dev.example.com'
resources:
- '^/users/bob/.*$'
subject: 'user:bob'
policy: 'two_factor'
session:
name: 'authelia_session'
expiration: '1h' # 1 hour
inactivity: '5m' # 5 minutes
domain: 'example.com'
redis:
host: '127.0.0.1'
port: 6379
high_availability:
sentinel_name: 'test'
regulation:
max_retries: 3
find_time: '2m'
ban_time: '5m'
storage:
mysql:
address: 'tcp://127.0.0.1:3306'
database: 'authelia'
username: 'authelia'
notifier:
smtp:
address: 'smtp://127.0.0.1:1025'
username: 'test'
sender: 'admin@example.com'
disable_require_tls: true
identity_providers:
oidc:
cors:
allowed_origins:
- 'https://google.com'
- 'https://example.com'
minimum_parameter_entropy: -1
clients:
- id: 'abc'
secret: '123'
consent_mode: 'explicit'
userinfo_signing_alg: 'none'
...

View File

@ -1,108 +1,111 @@
--- ---
default_redirection_url: https://home.example.com:8080/ default_redirection_url: 'https://home.example.com:8080/'
server: server:
host: 127.0.0.1 host: 127.0.0.1
port: 9091 port: 9091
log: log:
level: debug level: 'debug'
totp: totp:
issuer: authelia.com issuer: 'authelia.com'
duo_api: duo_api:
hostname: api-123456789.example.com hostname: 'api-123456789.example.com'
integration_key: ABCDEF integration_key: 'ABCDEF'
authentication_backend: authentication_backend:
ldap: ldap:
url: ldap://127.0.0.1 url: 'ldap://127.0.0.1'
base_dn: dc=example,dc=com base_dn: 'dc=example,dc=com'
username_attribute: uid additional_users_dn: 'ou=users'
additional_users_dn: ou=users users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
users_filter: (&({username_attribute}={input})(objectCategory=person)(objectClass=user)) additional_groups_dn: 'ou=groups'
additional_groups_dn: ou=groups groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
groups_filter: (&(member={dn})(objectClass=groupOfNames)) user: 'cn=admin,dc=example,dc=com'
group_name_attribute: cn attributes:
mail_attribute: mail mail: 'mail'
user: cn=admin,dc=example,dc=com username: 'uid'
group_name: 'cn'
access_control: access_control:
default_policy: deny default_policy: 'deny'
rules: rules:
# Rules applied to everyone # Rules applied to everyone
- domain: public.example.com - domain: 'public.example.com'
policy: bypass policy: 'bypass'
- domain: secure.example.com - domain: 'secure.example.com'
policy: one_factor policy: 'one_factor'
# Network based rule, if not provided any network matches. # Network based rule, if not provided any network matches.
networks: networks:
- 192.168.1.0/24 - '192.168.1.0/24'
- domain: secure.example.com - domain: 'secure.example.com'
policy: two_factor policy: 'two_factor'
- domain: [singlefactor.example.com, onefactor.example.com] - domain:
policy: one_factor - 'singlefactor.example.com'
- 'onefactor.example.com'
policy: 'one_factor'
# Rules applied to 'admins' group # Rules applied to 'admins' group
- domain: "mx2.mail.example.com" - domain: 'mx2.mail.example.com'
subject: "group:admins" subject: 'group:admins'
policy: deny policy: 'deny'
- domain: "*.example.com" - domain: '*.example.com'
subject: "group:admins" subject: 'group:admins'
policy: two_factor policy: 'two_factor'
# Rules applied to 'dev' group # Rules applied to 'dev' group
- domain: dev.example.com - domain: 'dev.example.com'
resources: resources:
- "^/groups/dev/.*$" - '^/groups/dev/.*$'
subject: "group:dev" subject: 'group:dev'
policy: two_factor policy: 'two_factor'
# Rules applied to user 'john' # Rules applied to user 'john'
- domain: dev.example.com - domain: 'dev.example.com'
resources: resources:
- "^/users/john/.*$" - '^/users/john/.*$'
subject: "user:john" subject: 'user:john'
policy: two_factor policy: 'two_factor'
# Rules applied to 'dev' group and user 'john' # Rules applied to 'dev' group and user 'john'
- domain: dev.example.com - domain: 'dev.example.com'
resources: resources:
- "^/deny-all.*$" - '^/deny-all.*$'
subject: ["group:dev", "user:john"] subject: ['group:dev', 'user:john']
policy: deny policy: 'deny'
# Rules applied to user 'harry' # Rules applied to user 'harry'
- domain: dev.example.com - domain: 'dev.example.com'
resources: resources:
- "^/users/harry/.*$" - '^/users/harry/.*$'
subject: "user:harry" subject: 'user:harry'
policy: two_factor policy: 'two_factor'
# Rules applied to user 'bob' # Rules applied to user 'bob'
- domain: "*.mail.example.com" - domain: '*.mail.example.com'
subject: "user:bob" subject: 'user:bob'
policy: two_factor policy: 'two_factor'
- domain: "dev.example.com" - domain: 'dev.example.com'
resources: resources:
- "^/users/bob/.*$" - '^/users/bob/.*$'
subject: "user:bob" subject: 'user:bob'
policy: two_factor policy: 'two_factor'
session: session:
name: authelia_session name: 'authelia_session'
expiration: 3600000 # 1 hour expiration: 3600000 # 1 hour
inactivity: 300000 # 5 minutes inactivity: 300000 # 5 minutes
domain: example.com domain: 'example.com'
redis: redis:
host: 127.0.0.1 host: 127.0.0.1
port: 6379 port: 6379
high_availability: high_availability:
sentinel_name: test sentinel_name: 'test'
regulation: regulation:
max_retries: 3 max_retries: 3
@ -113,12 +116,12 @@ storage:
mysql: mysql:
host: 127.0.0.1 host: 127.0.0.1
port: 3306 port: 3306
database: authelia database: 'authelia'
username: authelia username: 'authelia'
notifier: notifier:
smtp: smtp:
username: test username: 'test'
host: 127.0.0.1 host: 127.0.0.1
port: 1025 port: 1025
sender: admin@example.com sender: admin@example.com
@ -126,7 +129,7 @@ notifier:
identity_providers: identity_providers:
oidc: oidc:
hmac_secret: 1nb2j3kh1b23kjh1b23jh1b23j1h2b3 hmac_secret: '1nb2j3kh1b23kjh1b23jh1b23j1h2b3'
issuer_private_keys: issuer_private_keys:
keys: keys:
keya: keya:
@ -202,10 +205,10 @@ identity_providers:
-----END CERTIFICATE----- -----END CERTIFICATE-----
cors: cors:
allowed_origins: allowed_origins:
- https://google.com - 'https://google.com'
- https://example.com - 'https://example.com'
clients: clients:
- id: abc - id: 'abc'
secret: '123' secret: '123'
consent_mode: explicit consent_mode: 'explicit'
... ...

View File

@ -18,14 +18,15 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -18,14 +18,15 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -20,14 +20,16 @@ authentication_backend:
ldap: ldap:
address: 'ldap://127.0.0.1' address: 'ldap://127.0.0.1'
base_dn: 'dc=example,dc=com' base_dn: 'dc=example,dc=com'
username_attribute: 'uid'
additional_users_dn: 'ou=users' additional_users_dn: 'ou=users'
users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))' users_filter: '(&({username_attribute}={input})(objectCategory=person)(objectClass=user))'
additional_groups_dn: 'ou=groups' additional_groups_dn: 'ou=groups'
groups_filter: '(&(member={dn})(objectClass=groupOfNames))' groups_filter: '(&(member={dn})(objectClass=groupOfNames))'
group_name_attribute: 'cn'
mail_attribute: 'mail'
user: 'cn=admin,dc=example,dc=com' user: 'cn=admin,dc=example,dc=com'
attributes:
username: 'uid'
group_name: 'cn'
mail: 'mail'
access_control: access_control:
default_policy: 'deny' default_policy: 'deny'

View File

@ -364,7 +364,7 @@ func validateLDAPAuthenticationBackendImplementation(config *schema.Authenticati
case schema.LDAPImplementationGLAuth: case schema.LDAPImplementationGLAuth:
implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth implementation = &schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth
default: default:
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendImplementation, strJoinOr(validLDAPImplementations), config.LDAP.Implementation)) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendOptionMustBeOneOf, "implementation", strJoinOr(validLDAPImplementations), config.LDAP.Implementation))
} }
tlsconfig := &schema.TLSConfig{} tlsconfig := &schema.TLSConfig{}
@ -394,32 +394,44 @@ func setDefaultImplementationLDAPAuthenticationBackendProfileAttributes(config *
config.AdditionalUsersDN = implementation.AdditionalUsersDN config.AdditionalUsersDN = implementation.AdditionalUsersDN
} }
if ldapImplementationShouldSetStr(config.AdditionalGroupsDN, implementation.AdditionalGroupsDN) {
config.AdditionalGroupsDN = implementation.AdditionalGroupsDN
}
if ldapImplementationShouldSetStr(config.UsersFilter, implementation.UsersFilter) { if ldapImplementationShouldSetStr(config.UsersFilter, implementation.UsersFilter) {
config.UsersFilter = implementation.UsersFilter config.UsersFilter = implementation.UsersFilter
} }
if ldapImplementationShouldSetStr(config.UsernameAttribute, implementation.UsernameAttribute) { if ldapImplementationShouldSetStr(config.AdditionalGroupsDN, implementation.AdditionalGroupsDN) {
config.UsernameAttribute = implementation.UsernameAttribute config.AdditionalGroupsDN = implementation.AdditionalGroupsDN
}
if ldapImplementationShouldSetStr(config.DisplayNameAttribute, implementation.DisplayNameAttribute) {
config.DisplayNameAttribute = implementation.DisplayNameAttribute
}
if ldapImplementationShouldSetStr(config.MailAttribute, implementation.MailAttribute) {
config.MailAttribute = implementation.MailAttribute
} }
if ldapImplementationShouldSetStr(config.GroupsFilter, implementation.GroupsFilter) { if ldapImplementationShouldSetStr(config.GroupsFilter, implementation.GroupsFilter) {
config.GroupsFilter = implementation.GroupsFilter config.GroupsFilter = implementation.GroupsFilter
} }
if ldapImplementationShouldSetStr(config.GroupNameAttribute, implementation.GroupNameAttribute) { if ldapImplementationShouldSetStr(config.GroupSearchMode, implementation.GroupSearchMode) {
config.GroupNameAttribute = implementation.GroupNameAttribute config.GroupSearchMode = implementation.GroupSearchMode
}
if ldapImplementationShouldSetStr(config.Attributes.DistinguishedName, implementation.Attributes.DistinguishedName) {
config.Attributes.DistinguishedName = implementation.Attributes.DistinguishedName
}
if ldapImplementationShouldSetStr(config.Attributes.Username, implementation.Attributes.Username) {
config.Attributes.Username = implementation.Attributes.Username
}
if ldapImplementationShouldSetStr(config.Attributes.DisplayName, implementation.Attributes.DisplayName) {
config.Attributes.DisplayName = implementation.Attributes.DisplayName
}
if ldapImplementationShouldSetStr(config.Attributes.Mail, implementation.Attributes.Mail) {
config.Attributes.Mail = implementation.Attributes.Mail
}
if ldapImplementationShouldSetStr(config.Attributes.MemberOf, implementation.Attributes.MemberOf) {
config.Attributes.MemberOf = implementation.Attributes.MemberOf
}
if ldapImplementationShouldSetStr(config.Attributes.GroupName, implementation.Attributes.GroupName) {
config.Attributes.GroupName = implementation.Attributes.GroupName
} }
} }
@ -486,4 +498,32 @@ func validateLDAPRequiredParameters(config *schema.AuthenticationBackend, valida
} else if !strings.HasPrefix(config.LDAP.GroupsFilter, "(") || !strings.HasSuffix(config.LDAP.GroupsFilter, ")") { } else if !strings.HasPrefix(config.LDAP.GroupsFilter, "(") || !strings.HasSuffix(config.LDAP.GroupsFilter, ")") {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter)) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "groups_filter", config.LDAP.GroupsFilter, config.LDAP.GroupsFilter))
} }
validateLDAPGroupFilter(config, validator)
}
func validateLDAPGroupFilter(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP.GroupSearchMode == "" {
config.LDAP.GroupSearchMode = schema.LDAPGroupSearchModeFilter
}
if !utils.IsStringInSlice(config.LDAP.GroupSearchMode, validLDAPGroupSearchModes) {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendOptionMustBeOneOf, "group_search_mode", strJoinOr(validLDAPGroupSearchModes), config.LDAP.GroupSearchMode))
}
pMemberOfDN, pMemberOfRDN := strings.Contains(config.LDAP.GroupsFilter, "{memberof:dn}"), strings.Contains(config.LDAP.GroupsFilter, "{memberof:rdn}")
if config.LDAP.GroupSearchMode == schema.LDAPGroupSearchModeMemberOf {
if !pMemberOfDN && !pMemberOfRDN {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholderGroupSearchMode, "groups_filter", strJoinOr([]string{"{memberof:rdn}", "{memberof:dn}"}), config.LDAP.GroupSearchMode))
}
}
if pMemberOfDN && config.LDAP.Attributes.DistinguishedName == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "distinguished_name", strJoinOr([]string{"{memberof:dn}"})))
}
if (pMemberOfDN || pMemberOfRDN) && config.LDAP.Attributes.MemberOf == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "member_of", strJoinOr([]string{"{memberof:rdn}", "{memberof:dn}"})))
}
} }

View File

@ -577,7 +577,7 @@ func (suite *LDAPAuthenticationBackendSuite) SetupTest() {
suite.config.LDAP.User = testLDAPUser suite.config.LDAP.User = testLDAPUser
suite.config.LDAP.Password = testLDAPPassword suite.config.LDAP.Password = testLDAPPassword
suite.config.LDAP.BaseDN = testLDAPBaseDN suite.config.LDAP.BaseDN = testLDAPBaseDN
suite.config.LDAP.UsernameAttribute = "uid" suite.config.LDAP.Attributes.Username = "uid"
suite.config.LDAP.UsersFilter = "({username_attribute}={input})" suite.config.LDAP.UsersFilter = "({username_attribute}={input})"
suite.config.LDAP.GroupsFilter = "(cn={input})" suite.config.LDAP.GroupsFilter = "(cn={input})"
} }
@ -591,12 +591,12 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateCompleteConfigura
func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() { func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementationAndUsernameAttribute() {
suite.config.LDAP.Implementation = "" suite.config.LDAP.Implementation = ""
suite.config.LDAP.UsernameAttribute = "" suite.config.LDAP.Attributes.Username = ""
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation) suite.Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation)
suite.Equal(suite.config.LDAP.UsernameAttribute, schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.UsernameAttribute) suite.Equal(suite.config.LDAP.Attributes.Username, schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Attributes.Username)
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
} }
@ -743,7 +743,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseOnEmptyUsersFilter()
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() { func (suite *LDAPAuthenticationBackendSuite) TestShouldNotRaiseOnEmptyUsernameAttribute() {
suite.config.LDAP.UsernameAttribute = "" suite.config.LDAP.Attributes.Username = ""
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
@ -793,7 +793,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultGroupNameAttrib
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal("cn", suite.config.LDAP.GroupNameAttribute) suite.Equal("cn", suite.config.LDAP.Attributes.GroupName)
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() { func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute() {
@ -802,7 +802,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultMailAttribute()
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal("mail", suite.config.LDAP.MailAttribute) suite.Equal("mail", suite.config.LDAP.Attributes.Mail)
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() { func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttribute() {
@ -811,7 +811,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttr
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal("displayName", suite.config.LDAP.DisplayNameAttribute) suite.Equal("displayName", suite.config.LDAP.Attributes.DisplayName)
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() { func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {
@ -890,6 +890,64 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowSSL30() {
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured") suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: tls: option 'minimum_version' is invalid: minimum version is TLS1.0 but SSL3.0 was configured")
} }
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnBadSearchMode() {
suite.config.LDAP.GroupSearchMode = "memberOF"
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'group_search_mode' must be one of 'filter' or 'memberof' but it's configured as 'memberOF'")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldNoErrorOnPlaceholderSearchMode() {
suite.config.LDAP.GroupSearchMode = memberof
suite.config.LDAP.GroupsFilter = filterMemberOfRDN
suite.config.LDAP.Attributes.MemberOf = memberOf
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingPlaceholderSearchMode() {
suite.config.LDAP.GroupSearchMode = memberof
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: option 'groups_filter' must contain one of the '{memberof:rdn}' or '{memberof:dn}' placeholders when using a group_search_mode of 'memberof' but they're absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingDistinguishedNameDN() {
suite.config.LDAP.Attributes.DistinguishedName = ""
suite.config.LDAP.GroupsFilter = "(|({memberof:dn}))"
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 2)
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: attributes: option 'distinguished_name' must be provided when using the '{memberof:dn}' placeholder but it's absent")
suite.EqualError(suite.validator.Errors()[1], "authentication_backend: ldap: attributes: option 'member_of' must be provided when using the '{memberof:rdn}' or '{memberof:dn}' placeholder but it's absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldErrorOnMissingMemberOfRDN() {
suite.config.LDAP.Attributes.DistinguishedName = ""
suite.config.LDAP.GroupsFilter = filterMemberOfRDN
ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.Len(suite.validator.Warnings(), 0)
suite.Require().Len(suite.validator.Errors(), 1)
suite.EqualError(suite.validator.Errors()[0], "authentication_backend: ldap: attributes: option 'member_of' must be provided when using the '{memberof:rdn}' or '{memberof:dn}' placeholder but it's absent")
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowTLSVerMinGreaterThanVerMax() { func (suite *LDAPAuthenticationBackendSuite) TestShouldNotAllowTLSVerMinGreaterThanVerMax() {
suite.config.LDAP.TLS = &schema.TLSConfig{ suite.config.LDAP.TLS = &schema.TLSConfig{
MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13}, MinimumVersion: schema.TLSVersion{Value: tls.VersionTLS13},
@ -909,9 +967,7 @@ func TestLDAPAuthenticationBackend(t *testing.T) {
} }
type ActiveDirectoryAuthenticationBackendSuite struct { type ActiveDirectoryAuthenticationBackendSuite struct {
suite.Suite LDAPImplementationSuite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() { func (suite *ActiveDirectoryAuthenticationBackendSuite) SetupTest() {
@ -932,81 +988,30 @@ func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldSetActiveDirec
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal( suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2))"
suite.config.LDAP.UsernameAttribute = "cn" suite.config.LDAP.Attributes.Username = "cn"
suite.config.LDAP.MailAttribute = "userPrincipalName" suite.config.LDAP.Attributes.Mail = "userPrincipalName"
suite.config.LDAP.DisplayNameAttribute = "name" suite.config.LDAP.Attributes.DisplayName = "name"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=group)(objectCategory=group))"
suite.config.LDAP.GroupNameAttribute = "distinguishedName" suite.config.LDAP.Attributes.GroupName = "distinguishedName"
suite.config.LDAP.AdditionalUsersDN = "OU=test" suite.config.LDAP.AdditionalUsersDN = "OU=test"
suite.config.LDAP.AdditionalGroupsDN = "OU=grps" suite.config.LDAP.AdditionalGroupsDN = "OU=grps"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.GroupSearchMode = memberof
suite.config.LDAP.Attributes.DistinguishedName = "objectGUID"
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqual( suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.Timeout,
suite.config.LDAP.Timeout) suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
suite.NotEqual( suite.Equal("objectGUID", suite.config.LDAP.Attributes.DistinguishedName)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalUsersDN, suite.Equal(memberof, suite.config.LDAP.GroupSearchMode)
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithHTTP() { func (suite *ActiveDirectoryAuthenticationBackendSuite) TestShouldRaiseErrorOnInvalidURLWithHTTP() {
@ -1023,9 +1028,7 @@ func TestActiveDirectoryAuthenticationBackend(t *testing.T) {
} }
type RFC2307bisAuthenticationBackendSuite struct { type RFC2307bisAuthenticationBackendSuite struct {
suite.Suite LDAPImplementationSuite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *RFC2307bisAuthenticationBackendSuite) SetupTest() { func (suite *RFC2307bisAuthenticationBackendSuite) SetupTest() {
@ -1046,78 +1049,29 @@ func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldSetDefaults() {
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal( suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *RFC2307bisAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person))"
suite.config.LDAP.UsernameAttribute = "o" suite.config.LDAP.Attributes.Username = "o"
suite.config.LDAP.MailAttribute = "Email" suite.config.LDAP.Attributes.Mail = "Email"
suite.config.LDAP.DisplayNameAttribute = "Given" suite.config.LDAP.Attributes.DisplayName = "Given"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup)(objectClass=top))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup)(objectClass=top))"
suite.config.LDAP.GroupNameAttribute = "gid" suite.config.LDAP.Attributes.GroupName = "gid"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=OpenLDAP" suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=OpenLDAP"
suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=OpenLDAP" suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=OpenLDAP"
suite.config.LDAP.GroupSearchMode = memberof
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqual( suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
suite.config.LDAP.Timeout) suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
suite.NotEqual( suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalUsersDN, suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode)
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.Timeout,
suite.config.LDAP.Timeout)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationRFC2307bis.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func TestRFC2307bisAuthenticationBackend(t *testing.T) { func TestRFC2307bisAuthenticationBackend(t *testing.T) {
@ -1125,9 +1079,7 @@ func TestRFC2307bisAuthenticationBackend(t *testing.T) {
} }
type FreeIPAAuthenticationBackendSuite struct { type FreeIPAAuthenticationBackendSuite struct {
suite.Suite LDAPImplementationSuite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *FreeIPAAuthenticationBackendSuite) SetupTest() { func (suite *FreeIPAAuthenticationBackendSuite) SetupTest() {
@ -1148,75 +1100,29 @@ func (suite *FreeIPAAuthenticationBackendSuite) TestShouldSetDefaults() {
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal( suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *FreeIPAAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *FreeIPAAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=person)(!(nsAccountLock=TRUE)))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=person)(!(nsAccountLock=TRUE)))"
suite.config.LDAP.UsernameAttribute = "dn" suite.config.LDAP.Attributes.Username = "dn"
suite.config.LDAP.MailAttribute = "email" suite.config.LDAP.Attributes.Mail = "email"
suite.config.LDAP.DisplayNameAttribute = "gecos" suite.config.LDAP.Attributes.DisplayName = "gecos"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixgroup))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixgroup))"
suite.config.LDAP.GroupNameAttribute = "groupName" suite.config.LDAP.GroupSearchMode = schema.LDAPGroupSearchModeMemberOf
suite.config.LDAP.Attributes.GroupName = "groupName"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.AdditionalUsersDN = "OU=people" suite.config.LDAP.AdditionalUsersDN = "OU=people"
suite.config.LDAP.AdditionalGroupsDN = "OU=grp" suite.config.LDAP.AdditionalGroupsDN = "OU=grp"
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqual( suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.Timeout,
suite.config.LDAP.Timeout) suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
suite.NotEqual( suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalUsersDN, suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode)
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func TestFreeIPAAuthenticationBackend(t *testing.T) { func TestFreeIPAAuthenticationBackend(t *testing.T) {
@ -1224,9 +1130,7 @@ func TestFreeIPAAuthenticationBackend(t *testing.T) {
} }
type LLDAPAuthenticationBackendSuite struct { type LLDAPAuthenticationBackendSuite struct {
suite.Suite LDAPImplementationSuite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *LLDAPAuthenticationBackendSuite) SetupTest() { func (suite *LLDAPAuthenticationBackendSuite) SetupTest() {
@ -1247,78 +1151,29 @@ func (suite *LLDAPAuthenticationBackendSuite) TestShouldSetDefaults() {
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal( suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *LLDAPAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *LLDAPAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(nsAccountLock=TRUE)))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(nsAccountLock=TRUE)))"
suite.config.LDAP.UsernameAttribute = "username" suite.config.LDAP.Attributes.Username = "username"
suite.config.LDAP.MailAttribute = "m" suite.config.LDAP.Attributes.Mail = "m"
suite.config.LDAP.DisplayNameAttribute = "fn" suite.config.LDAP.Attributes.DisplayName = "fn"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.GroupsFilter = "(&(member={dn})(!(objectClass=posixGroup)))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(!(objectClass=posixGroup)))"
suite.config.LDAP.GroupNameAttribute = "grpz" suite.config.LDAP.Attributes.GroupName = "grpz"
suite.config.LDAP.AdditionalUsersDN = "OU=no" suite.config.LDAP.AdditionalUsersDN = "OU=no"
suite.config.LDAP.AdditionalGroupsDN = "OU=yes" suite.config.LDAP.AdditionalGroupsDN = "OU=yes"
suite.config.LDAP.GroupSearchMode = memberof
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqual( suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
suite.config.LDAP.Timeout) suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
suite.NotEqual( suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalUsersDN, suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode)
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.Timeout,
suite.config.LDAP.Timeout)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationLLDAP.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func TestLLDAPAuthenticationBackend(t *testing.T) { func TestLLDAPAuthenticationBackend(t *testing.T) {
@ -1326,9 +1181,7 @@ func TestLLDAPAuthenticationBackend(t *testing.T) {
} }
type GLAuthAuthenticationBackendSuite struct { type GLAuthAuthenticationBackendSuite struct {
suite.Suite LDAPImplementationSuite
config schema.AuthenticationBackend
validator *schema.StructValidator
} }
func (suite *GLAuthAuthenticationBackendSuite) SetupTest() { func (suite *GLAuthAuthenticationBackendSuite) SetupTest() {
@ -1349,80 +1202,80 @@ func (suite *GLAuthAuthenticationBackendSuite) TestShouldSetDefaults() {
suite.Len(suite.validator.Warnings(), 0) suite.Len(suite.validator.Warnings(), 0)
suite.Len(suite.validator.Errors(), 0) suite.Len(suite.validator.Errors(), 0)
suite.Equal( suite.EqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
suite.config.LDAP.Timeout)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalUsersDN,
suite.config.LDAP.AdditionalUsersDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.Equal(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func (suite *GLAuthAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() { func (suite *GLAuthAuthenticationBackendSuite) TestShouldOnlySetDefaultsIfNotManuallyConfigured() {
suite.config.LDAP.Timeout = time.Second * 2 suite.config.LDAP.Timeout = time.Second * 2
suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(accountStatus=inactive)))" suite.config.LDAP.UsersFilter = "(&({username_attribute}={input})(objectClass=Person)(!(accountStatus=inactive)))"
suite.config.LDAP.UsernameAttribute = "description" suite.config.LDAP.Attributes.Username = "description"
suite.config.LDAP.MailAttribute = "sender" suite.config.LDAP.Attributes.Mail = "sender"
suite.config.LDAP.DisplayNameAttribute = "given" suite.config.LDAP.Attributes.DisplayName = "given"
suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup))" suite.config.LDAP.GroupsFilter = "(&(member={dn})(objectClass=posixGroup))"
suite.config.LDAP.GroupNameAttribute = "grp" suite.config.LDAP.Attributes.GroupName = "grp"
suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=GlAuth" suite.config.LDAP.AdditionalUsersDN = "OU=users,OU=GlAuth"
suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=GLAuth" suite.config.LDAP.AdditionalGroupsDN = "OU=groups,OU=GLAuth"
suite.config.LDAP.Attributes.MemberOf = member
suite.config.LDAP.GroupSearchMode = memberof
ValidateAuthenticationBackend(&suite.config, suite.validator) ValidateAuthenticationBackend(&suite.config, suite.validator)
suite.NotEqual( suite.NotEqualImplementationDefaults(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
suite.config.LDAP.Timeout) suite.Equal(member, suite.config.LDAP.Attributes.MemberOf)
suite.NotEqual( suite.Equal("", suite.config.LDAP.Attributes.DistinguishedName)
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalUsersDN, suite.Equal(schema.LDAPGroupSearchModeMemberOf, suite.config.LDAP.GroupSearchMode)
suite.config.LDAP.AdditionalUsersDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.AdditionalGroupsDN,
suite.config.LDAP.AdditionalGroupsDN)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.Timeout,
suite.config.LDAP.Timeout)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsersFilter,
suite.config.LDAP.UsersFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.UsernameAttribute,
suite.config.LDAP.UsernameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.DisplayNameAttribute,
suite.config.LDAP.DisplayNameAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.MailAttribute,
suite.config.LDAP.MailAttribute)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupsFilter,
suite.config.LDAP.GroupsFilter)
suite.NotEqual(
schema.DefaultLDAPAuthenticationBackendConfigurationImplementationGLAuth.GroupNameAttribute,
suite.config.LDAP.GroupNameAttribute)
} }
func TestGLAuthAuthenticationBackend(t *testing.T) { func TestGLAuthAuthenticationBackend(t *testing.T) {
suite.Run(t, new(GLAuthAuthenticationBackendSuite)) suite.Run(t, new(GLAuthAuthenticationBackendSuite))
} }
type LDAPImplementationSuite struct {
suite.Suite
config schema.AuthenticationBackend
validator *schema.StructValidator
}
func (suite *LDAPImplementationSuite) EqualImplementationDefaults(expected schema.LDAPAuthenticationBackend) {
suite.Equal(expected.Timeout, suite.config.LDAP.Timeout)
suite.Equal(expected.AdditionalUsersDN, suite.config.LDAP.AdditionalUsersDN)
suite.Equal(expected.AdditionalGroupsDN, suite.config.LDAP.AdditionalGroupsDN)
suite.Equal(expected.UsersFilter, suite.config.LDAP.UsersFilter)
suite.Equal(expected.GroupsFilter, suite.config.LDAP.GroupsFilter)
suite.Equal(expected.GroupSearchMode, suite.config.LDAP.GroupSearchMode)
suite.Equal(expected.Attributes.DistinguishedName, suite.config.LDAP.Attributes.DistinguishedName)
suite.Equal(expected.Attributes.Username, suite.config.LDAP.Attributes.Username)
suite.Equal(expected.Attributes.DisplayName, suite.config.LDAP.Attributes.DisplayName)
suite.Equal(expected.Attributes.Mail, suite.config.LDAP.Attributes.Mail)
suite.Equal(expected.Attributes.MemberOf, suite.config.LDAP.Attributes.MemberOf)
suite.Equal(expected.Attributes.GroupName, suite.config.LDAP.Attributes.GroupName)
}
func (suite *LDAPImplementationSuite) NotEqualImplementationDefaults(expected schema.LDAPAuthenticationBackend) {
suite.NotEqual(expected.Timeout, suite.config.LDAP.Timeout)
suite.NotEqual(expected.UsersFilter, suite.config.LDAP.UsersFilter)
suite.NotEqual(expected.GroupsFilter, suite.config.LDAP.GroupsFilter)
suite.NotEqual(expected.GroupSearchMode, suite.config.LDAP.GroupSearchMode)
suite.NotEqual(expected.Attributes.Username, suite.config.LDAP.Attributes.Username)
suite.NotEqual(expected.Attributes.DisplayName, suite.config.LDAP.Attributes.DisplayName)
suite.NotEqual(expected.Attributes.Mail, suite.config.LDAP.Attributes.Mail)
suite.NotEqual(expected.Attributes.GroupName, suite.config.LDAP.Attributes.GroupName)
if expected.Attributes.DistinguishedName != "" {
suite.NotEqual(expected.Attributes.DistinguishedName, suite.config.LDAP.Attributes.DistinguishedName)
}
if expected.AdditionalUsersDN != "" {
suite.NotEqual(expected.AdditionalUsersDN, suite.config.LDAP.AdditionalUsersDN)
}
if expected.AdditionalGroupsDN != "" {
suite.NotEqual(expected.AdditionalGroupsDN, suite.config.LDAP.AdditionalGroupsDN)
}
if expected.Attributes.MemberOf != "" {
suite.NotEqual(expected.Attributes.MemberOf, suite.config.LDAP.Attributes.MemberOf)
}
}

View File

@ -100,7 +100,7 @@ const (
errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required" errFmtLDAPAuthBackendMissingOption = "authentication_backend: ldap: option '%s' is required"
errFmtLDAPAuthBackendTLSConfigInvalid = "authentication_backend: ldap: tls: %w" errFmtLDAPAuthBackendTLSConfigInvalid = "authentication_backend: ldap: tls: %w"
errFmtLDAPAuthBackendImplementation = "authentication_backend: ldap: option 'implementation' " + errFmtLDAPAuthBackendOptionMustBeOneOf = "authentication_backend: ldap: option '%s' " +
errSuffixMustBeOneOf errSuffixMustBeOneOf
errFmtLDAPAuthBackendFilterReplacedPlaceholders = "authentication_backend: ldap: option " + errFmtLDAPAuthBackendFilterReplacedPlaceholders = "authentication_backend: ldap: option " +
"'%s' has an invalid placeholder: '%s' has been removed, please use '%s' instead" "'%s' has an invalid placeholder: '%s' has been removed, please use '%s' instead"
@ -109,6 +109,10 @@ const (
"'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'" "'%s' must contain enclosing parenthesis: '%s' should probably be '(%s)'"
errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " + errFmtLDAPAuthBackendFilterMissingPlaceholder = "authentication_backend: ldap: option " +
"'%s' must contain the placeholder '{%s}' but it's absent" "'%s' must contain the placeholder '{%s}' but it's absent"
errFmtLDAPAuthBackendFilterMissingPlaceholderGroupSearchMode = "authentication_backend: ldap: option " +
"'%s' must contain one of the %s placeholders when using a group_search_mode of '%s' but they're absent"
errFmtLDAPAuthBackendFilterMissingAttribute = "authentication_backend: ldap: attributes: option " +
"'%s' must be provided when using the %s placeholder but it's absent"
) )
// TOTP Error constants. // TOTP Error constants.
@ -374,17 +378,6 @@ const (
operatorNotPattern = "not pattern" operatorNotPattern = "not pattern"
) )
var (
validLDAPImplementations = []string{
schema.LDAPImplementationCustom,
schema.LDAPImplementationActiveDirectory,
schema.LDAPImplementationRFC2307bis,
schema.LDAPImplementationFreeIPA,
schema.LDAPImplementationLLDAP,
schema.LDAPImplementationGLAuth,
}
)
const ( const (
legacy = "legacy" legacy = "legacy"
authzImplementationLegacy = "Legacy" authzImplementationLegacy = "Legacy"
@ -400,6 +393,22 @@ var (
validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"} validAuthzAuthnStrategies = []string{"CookieSession", "HeaderAuthorization", "HeaderProxyAuthorization", "HeaderAuthRequestProxyAuthorization", "HeaderLegacy"}
) )
var (
validLDAPImplementations = []string{
schema.LDAPImplementationCustom,
schema.LDAPImplementationActiveDirectory,
schema.LDAPImplementationRFC2307bis,
schema.LDAPImplementationFreeIPA,
schema.LDAPImplementationLLDAP,
schema.LDAPImplementationGLAuth,
}
validLDAPGroupSearchModes = []string{
schema.LDAPGroupSearchModeFilter,
schema.LDAPGroupSearchModeMemberOf,
}
)
var ( var (
validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"} validArgon2Variants = []string{"argon2id", "id", "argon2i", "i", "argon2d", "d"}
validSHA2CryptVariants = []string{digestSHA256, digestSHA512} validSHA2CryptVariants = []string{digestSHA256, digestSHA512}

View File

@ -13,6 +13,11 @@ const (
testLDAPURL = "ldap://ldap" testLDAPURL = "ldap://ldap"
testLDAPUser = "user" testLDAPUser = "user"
testEncryptionKey = "a_not_so_secure_encryption_key" testEncryptionKey = "a_not_so_secure_encryption_key"
member = "member"
memberof = "memberof"
memberOf = "memberOf"
filterMemberOfRDN = "(|({memberof:rdn}))"
) )
const ( const (

View File

@ -21,16 +21,19 @@ authentication_backend:
ldap: ldap:
address: 'ldap://openldap' address: 'ldap://openldap'
base_dn: dc=example,dc=com base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectClass=person)) users_filter: (&({username_attribute}={input})(objectClass=person))
additional_groups_dn: ou=groups additional_groups_dn: ou=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames)) groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
password: password password: password
attributes:
distinguished_name: 'distinguishedName'
username: 'uid'
display_name: 'displayName'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
access_control: access_control:
default_policy: deny default_policy: deny

View File

@ -22,16 +22,19 @@ authentication_backend:
tls: tls:
skip_verify: true skip_verify: true
base_dn: dc=example,dc=com base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users additional_users_dn: ou=users
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(objectClass=inetOrgPerson)) # yamllint disable-line rule:line-length users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(objectClass=inetOrgPerson)) # yamllint disable-line rule:line-length
additional_groups_dn: ou=groups additional_groups_dn: ou=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames)) groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
user: cn=pwmanager,dc=example,dc=com user: cn=pwmanager,dc=example,dc=com
password: password password: password
attributes:
distinguished_name: ''
username: 'uid'
display_name: 'displayName'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
session: session:
secret: unsecure_session_secret secret: unsecure_session_secret

View File

@ -20,15 +20,18 @@ authentication_backend:
tls: tls:
skip_verify: true skip_verify: true
base_dn: dc=example,dc=com base_dn: dc=example,dc=com
username_attribute: uid
additional_users_dn: ou=users additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectClass=person)) users_filter: (&({username_attribute}={input})(objectClass=person))
additional_groups_dn: ou=groups additional_groups_dn: ou=groups
groups_filter: (&(member={dn})(objectClass=groupOfNames)) groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
display_name_attribute: displayName
user: cn=admin,dc=example,dc=com user: cn=admin,dc=example,dc=com
attributes:
distinguished_name: ''
username: 'uid'
display_name: 'displayName'
mail: 'mail'
member_of: 'memberOf'
group_name: 'cn'
access_control: access_control:
default_policy: deny default_policy: deny