perf(authentication): improve ldap dynamic replacement performance (#2239)

This change means we only check the filters for the existence of placeholders that cannot be replaced at startup. We then utilized cached results of that lookup for subsequent replacements.
pull/2240/head^2
James Elliott 2021-08-05 14:17:07 +10:00 committed by GitHub
parent c5c6bda8b0
commit a3b14871ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 312 additions and 217 deletions

View File

@ -183,10 +183,8 @@ authentication_backend:
additional_users_dn: ou=users
## The users filter used in search queries to find the user profile based on input filled in login form.
## Various placeholders are available in the user filter:
## - {input} is a placeholder replaced by what the user inputs in the login form.
## - {username_attribute} is a mandatory placeholder replaced by what is configured in `username_attribute`.
## - {mail_attribute} is a placeholder replaced by what is configured in `mail_attribute`.
## Various placeholders are available in the user filter which you can read about in the documentation which can
## be found at: https://www.authelia.com/docs/configuration/authentication/ldap.html#users-filter-replacements
##
## Recommended settings are as follows:
## - Microsoft Active Directory: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
@ -202,16 +200,13 @@ authentication_backend:
## i.e. with this set to OU=Groups and base_dn set to DC=a,DC=com; OU=Groups,DC=a,DC=com is searched for groups.
additional_groups_dn: ou=groups
## The groups filter used in search queries to find the groups of the user.
## - {input} is a placeholder replaced by what the user inputs in the login form.
## - {username} is a placeholder replace by the username stored in LDAP (based on `username_attribute`).
## - {dn} is a matcher replaced by the user distinguished name, aka, user DN.
## - {username_attribute} is a placeholder replaced by what is configured in `username_attribute`.
## - {mail_attribute} is a placeholder replaced by what is configured in `mail_attribute`.
## The groups filter used in search queries to find the groups based on relevant authenticated user.
## Various placeholders are available in the groups filter which you can read about in the documentation which can
## be found at: https://www.authelia.com/docs/configuration/authentication/ldap.html#groups-filter-replacements
##
## If your groups use the `groupOfUniqueNames` structure use this instead:
## (&(uniquemember={dn})(objectclass=groupOfUniqueNames))
groups_filter: (&(member={dn})(objectclass=groupOfNames))
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
groups_filter: (&(member={dn})(objectClass=groupOfNames))
## The attribute holding the name of the group.
# group_name_attribute: cn
@ -221,7 +216,7 @@ authentication_backend:
# 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
# display_name_attribute: displayName
## The username and password of the admin user.
user: cn=admin,dc=example,dc=com

View File

@ -29,10 +29,10 @@ authentication_backend:
additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectClass=person))
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
display_name_attribute: displayName
user: cn=admin,dc=example,dc=com
password: password
```
@ -182,6 +182,28 @@ must be used if you wish to allow users to change or reset their password as Act
uses a custom attribute for this, and an input format other implementations do not use. The long term
intention of this is to have logical defaults for various RFC implementations of LDAP.
### Filter replacements
Various replacements occur in the user and groups filter. The replacements either occur at startup or upon an LDAP
search.
#### Users filter replacements
|Placeholder |Phase |Replacement |
|:----------------------:|:-----:|:--------------------------------------------------------------:|
|{username_attribute} |startup|The [username attribute](#username_attribute) configured |
|{mail_attribute} |startup|The [mail attribute](#mail_attribute) configured |
|{display_name_attribute}|startup|The [display name attribute](#display_name_attribute) configured|
|{input} |search |The input into the username field |
#### Groups filter replacements
|Placeholder |Phase |Replacement |
|:----------------------:|:-----:|:-------------------------------------------------------------------------:|
|{input} |search |The input into the username field |
|{username} |search |The username from the profile lookup obtained from the [username attribute]|
|{dn} |search |The distinguished name from the profile lookup |
### Defaults
The below tables describes the current attribute defaults for each implementation.
@ -192,8 +214,8 @@ described by the Username column.
|Implementation |Username |Display Name|Mail|Group Name|
|:-------------:|:------------:|:----------:|:--:|:--------:|
|custom |n/a |displayname |mail|cn |
|activedirectory|sAMAccountName|displayname |mail|cn |
|custom |n/a |displayName |mail|cn |
|activedirectory|sAMAccountName|displayName |mail|cn |
#### Filter defaults
@ -245,3 +267,7 @@ As of versions > `4.24.0` the `users_filter` must include the `username_attribut
result in Authelia throwing an error.
In versions <= `4.24.0` not including the `username_attribute` placeholder will cause issues with the session refresh
and will result in session resets when the refresh interval has expired, default of 5 minutes.
[LDAP GeneralizedTime]: https://ldapwiki.com/wiki/GeneralizedTime
[username attribute]: #username_attribute
[TechNet wiki]: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx

View File

@ -30,6 +30,12 @@ const (
ldapOIDPasswdModifyExtension = "1.3.6.1.4.1.4203.1.11.1" // http://oidref.com/1.3.6.1.4.1.4203.1.11.1
)
const (
ldapPlaceholderInput = "{input}"
ldapPlaceholderDistinguishedName = "{dn}"
ldapPlaceholderUsername = "{username}"
)
// PossibleMethods is the set of all possible 2FA methods.
var PossibleMethods = []string{TOTP, U2F, Push}

View File

@ -15,17 +15,28 @@ import (
"github.com/authelia/authelia/internal/utils"
)
// LDAPUserProvider is a provider using a LDAP or AD as a user database.
// LDAPUserProvider is a UserProvider that connects to LDAP servers like ActiveDirectory, OpenLDAP, OpenDJ, FreeIPA, etc.
type LDAPUserProvider struct {
configuration schema.LDAPAuthenticationBackendConfiguration
tlsConfig *tls.Config
dialOpts ldap.DialOpt
logger *logrus.Logger
connectionFactory LDAPConnectionFactory
usersBaseDN string
groupsBaseDN string
// Automatically detected ldap features.
supportExtensionPasswdModify bool
// Dynamically generated users values.
usersBaseDN string
usersAttributes []string
usersFilterReplacementInput bool
// Dynamically generated groups values.
groupsBaseDN string
groupsAttributes []string
groupsFilterReplacementInput bool
groupsFilterReplacementUsername bool
groupsFilterReplacementDN bool
}
// NewLDAPUserProvider creates a new instance of LDAPUserProvider.
@ -72,74 +83,12 @@ func newLDAPUserProvider(configuration schema.LDAPAuthenticationBackendConfigura
connectionFactory: factory,
}
provider.parseDynamicConfiguration()
provider.parseDynamicUsersConfiguration()
provider.parseDynamicGroupsConfiguration()
return provider
}
func (p *LDAPUserProvider) parseDynamicConfiguration() {
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{username_attribute}", p.configuration.UsernameAttribute)
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{mail_attribute}", p.configuration.MailAttribute)
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{display_name_attribute}", p.configuration.DisplayNameAttribute)
p.logger.Tracef("Dynamically generated users filter is %s", p.configuration.UsersFilter)
if p.configuration.AdditionalUsersDN != "" {
p.usersBaseDN = p.configuration.AdditionalUsersDN + "," + p.configuration.BaseDN
} else {
p.usersBaseDN = p.configuration.BaseDN
}
p.logger.Tracef("Dynamically generated users BaseDN is %s", p.usersBaseDN)
if p.configuration.AdditionalGroupsDN != "" {
p.groupsBaseDN = ldap.EscapeFilter(p.configuration.AdditionalGroupsDN + "," + p.configuration.BaseDN)
} else {
p.groupsBaseDN = p.configuration.BaseDN
}
p.logger.Tracef("Dynamically generated groups BaseDN is %s", p.groupsBaseDN)
}
func (p *LDAPUserProvider) checkServer() (err error) {
conn, err := p.connect(p.configuration.User, p.configuration.Password)
if err != nil {
return err
}
defer conn.Close()
searchRequest := ldap.NewSearchRequest("", ldap.ScopeBaseObject, ldap.NeverDerefAliases,
1, 0, false, "(objectClass=*)", []string{ldapSupportedExtensionAttribute}, nil)
sr, err := conn.Search(searchRequest)
if err != nil {
return err
}
if len(sr.Entries) != 1 {
return nil
}
// Iterate the attribute values to see what the server supports.
for _, attr := range sr.Entries[0].Attributes {
if attr.Name == ldapSupportedExtensionAttribute {
p.logger.Tracef("LDAP Supported Extension OIDs: %s", strings.Join(attr.Values, ", "))
for _, oid := range attr.Values {
if oid == ldapOIDPasswdModifyExtension {
p.supportExtensionPasswdModify = true
break
}
}
break
}
}
return nil
}
func (p *LDAPUserProvider) connect(userDN string, password string) (LDAPConnection, error) {
conn, err := p.connectionFactory.DialURL(p.configuration.URL, p.dialOpts)
if err != nil {
@ -197,34 +146,31 @@ type ldapUserProfile struct {
Username string
}
func (p *LDAPUserProvider) resolveUsersFilter(userFilter string, inputUsername string) string {
inputUsername = p.ldapEscape(inputUsername)
func (p *LDAPUserProvider) resolveUsersFilter(inputUsername string) (filter string) {
filter = p.configuration.UsersFilter
if p.usersFilterReplacementInput {
// The {input} placeholder is replaced by the users username input.
userFilter = strings.ReplaceAll(userFilter, "{input}", inputUsername)
filter = strings.ReplaceAll(filter, ldapPlaceholderInput, p.ldapEscape(inputUsername))
}
p.logger.Tracef("Computed user filter is %s", userFilter)
p.logger.Tracef("Computed user filter is %s", filter)
return userFilter
return filter
}
func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername string) (*ldapUserProfile, error) {
userFilter := p.resolveUsersFilter(p.configuration.UsersFilter, inputUsername)
attributes := []string{"dn",
p.configuration.DisplayNameAttribute,
p.configuration.MailAttribute,
p.configuration.UsernameAttribute}
userFilter := p.resolveUsersFilter(inputUsername)
// Search for the given username.
searchRequest := ldap.NewSearchRequest(
p.usersBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
1, 0, false, userFilter, attributes, nil,
1, 0, false, userFilter, p.usersAttributes, nil,
)
sr, err := conn.Search(searchRequest)
if err != nil {
return nil, fmt.Errorf("Cannot find user DN of user %s. Cause: %s", inputUsername, err)
return nil, fmt.Errorf("cannot find user DN of user '%s'. Cause: %w", inputUsername, err)
}
if len(sr.Entries) == 0 {
@ -232,7 +178,7 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
}
if len(sr.Entries) > 1 {
return nil, fmt.Errorf("Multiple users %s found", inputUsername)
return nil, fmt.Errorf("multiple users %s found", inputUsername)
}
userProfile := ldapUserProfile{
@ -250,7 +196,7 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
if attr.Name == p.configuration.UsernameAttribute {
if len(attr.Values) != 1 {
return nil, fmt.Errorf("User %s cannot have multiple value for attribute %s",
return nil, fmt.Errorf("user '%s' cannot have multiple value for attribute '%s'",
inputUsername, p.configuration.UsernameAttribute)
}
@ -259,26 +205,33 @@ func (p *LDAPUserProvider) getUserProfile(conn LDAPConnection, inputUsername str
}
if userProfile.DN == "" {
return nil, fmt.Errorf("No DN has been found for user %s", inputUsername)
return nil, fmt.Errorf("no DN has been found for user %s", inputUsername)
}
return &userProfile, nil
}
func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ldapUserProfile) (string, error) { //nolint:unparam
inputUsername = p.ldapEscape(inputUsername)
func (p *LDAPUserProvider) resolveGroupsFilter(inputUsername string, profile *ldapUserProfile) (filter string, err error) { //nolint:unparam
filter = p.configuration.GroupsFilter
if p.groupsFilterReplacementInput {
// The {input} placeholder is replaced by the users username input.
groupFilter := strings.ReplaceAll(p.configuration.GroupsFilter, "{input}", inputUsername)
if profile != nil {
groupFilter = strings.ReplaceAll(groupFilter, "{username}", ldap.EscapeFilter(profile.Username))
groupFilter = strings.ReplaceAll(groupFilter, "{dn}", ldap.EscapeFilter(profile.DN))
filter = strings.ReplaceAll(p.configuration.GroupsFilter, ldapPlaceholderInput, p.ldapEscape(inputUsername))
}
p.logger.Tracef("Computed groups filter is %s", groupFilter)
if profile != nil {
if p.groupsFilterReplacementUsername {
filter = strings.ReplaceAll(filter, ldapPlaceholderUsername, ldap.EscapeFilter(profile.Username))
}
return groupFilter, nil
if p.groupsFilterReplacementDN {
filter = strings.ReplaceAll(filter, ldapPlaceholderDistinguishedName, ldap.EscapeFilter(profile.DN))
}
}
p.logger.Tracef("Computed groups filter is %s", filter)
return filter, nil
}
// GetDetails retrieve the groups a user belongs to.
@ -296,19 +249,19 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error
groupsFilter, err := p.resolveGroupsFilter(inputUsername, profile)
if err != nil {
return nil, fmt.Errorf("Unable to create group filter for user %s. Cause: %s", inputUsername, err)
return nil, fmt.Errorf("unable to create group filter for user '%s'. Cause: %w", inputUsername, err)
}
// Search for the given username.
searchGroupRequest := ldap.NewSearchRequest(
p.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
0, 0, false, groupsFilter, []string{p.configuration.GroupNameAttribute}, nil,
0, 0, false, groupsFilter, p.groupsAttributes, nil,
)
sr, err := conn.Search(searchGroupRequest)
if err != nil {
return nil, fmt.Errorf("Unable to retrieve groups of user %s. Cause: %s", inputUsername, err)
return nil, fmt.Errorf("unable to retrieve groups of user '%s'. Cause: %w", inputUsername, err)
}
groups := make([]string, 0)
@ -318,6 +271,7 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error
p.logger.Warningf("No groups retrieved from LDAP for user %s", inputUsername)
break
}
// Append all values of the document. Normally there should be only one per document.
groups = append(groups, res.Attributes[0].Values...)
}
@ -334,14 +288,14 @@ func (p *LDAPUserProvider) GetDetails(inputUsername string) (*UserDetails, error
func (p *LDAPUserProvider) UpdatePassword(inputUsername string, newPassword string) error {
conn, err := p.connect(p.configuration.User, p.configuration.Password)
if err != nil {
return fmt.Errorf("Unable to update password. Cause: %s", err)
return fmt.Errorf("unable to update password. Cause: %w", err)
}
defer conn.Close()
profile, err := p.getUserProfile(conn, inputUsername)
if err != nil {
return fmt.Errorf("Unable to update password. Cause: %s", err)
return fmt.Errorf("unable to update password. Cause: %w", err)
}
switch {
@ -370,7 +324,7 @@ func (p *LDAPUserProvider) UpdatePassword(inputUsername string, newPassword stri
}
if err != nil {
return fmt.Errorf("Unable to update password. Cause: %s", err)
return fmt.Errorf("unable to update password. Cause: %w", err)
}
return nil

View File

@ -0,0 +1,103 @@
package authentication
import (
"strings"
"github.com/go-ldap/ldap/v3"
)
func (p *LDAPUserProvider) checkServer() (err error) {
conn, err := p.connect(p.configuration.User, p.configuration.Password)
if err != nil {
return err
}
defer conn.Close()
searchRequest := ldap.NewSearchRequest("", ldap.ScopeBaseObject, ldap.NeverDerefAliases,
1, 0, false, "(objectClass=*)", []string{ldapSupportedExtensionAttribute}, nil)
sr, err := conn.Search(searchRequest)
if err != nil {
return err
}
if len(sr.Entries) != 1 {
return nil
}
// Iterate the attribute values to see what the server supports.
for _, attr := range sr.Entries[0].Attributes {
if attr.Name == ldapSupportedExtensionAttribute {
p.logger.Tracef("LDAP Supported Extension OIDs: %s", strings.Join(attr.Values, ", "))
for _, oid := range attr.Values {
if oid == ldapOIDPasswdModifyExtension {
p.supportExtensionPasswdModify = true
break
}
}
break
}
}
return nil
}
func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{username_attribute}", p.configuration.UsernameAttribute)
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{mail_attribute}", p.configuration.MailAttribute)
p.configuration.UsersFilter = strings.ReplaceAll(p.configuration.UsersFilter, "{display_name_attribute}", p.configuration.DisplayNameAttribute)
p.logger.Tracef("Dynamically generated users filter is %s", p.configuration.UsersFilter)
p.usersAttributes = []string{
p.configuration.DisplayNameAttribute,
p.configuration.MailAttribute,
p.configuration.UsernameAttribute,
}
if p.configuration.AdditionalUsersDN != "" {
p.usersBaseDN = p.configuration.AdditionalUsersDN + "," + p.configuration.BaseDN
} else {
p.usersBaseDN = p.configuration.BaseDN
}
p.logger.Tracef("Dynamically generated users BaseDN is %s", p.usersBaseDN)
if strings.Contains(p.configuration.UsersFilter, ldapPlaceholderInput) {
p.usersFilterReplacementInput = true
}
p.logger.Tracef("Detected user filter replacements that need to be resolved per lookup are: %s=%v",
ldapPlaceholderInput, p.usersFilterReplacementInput)
}
func (p *LDAPUserProvider) parseDynamicGroupsConfiguration() {
p.groupsAttributes = []string{
p.configuration.GroupNameAttribute,
}
if p.configuration.AdditionalGroupsDN != "" {
p.groupsBaseDN = ldap.EscapeFilter(p.configuration.AdditionalGroupsDN + "," + p.configuration.BaseDN)
} else {
p.groupsBaseDN = p.configuration.BaseDN
}
p.logger.Tracef("Dynamically generated groups BaseDN is %s", p.groupsBaseDN)
if strings.Contains(p.configuration.GroupsFilter, ldapPlaceholderInput) {
p.groupsFilterReplacementInput = true
}
if strings.Contains(p.configuration.GroupsFilter, ldapPlaceholderUsername) {
p.groupsFilterReplacementUsername = true
}
if strings.Contains(p.configuration.GroupsFilter, ldapPlaceholderDistinguishedName) {
p.groupsFilterReplacementDN = true
}
p.logger.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)
}

View File

@ -174,7 +174,7 @@ func TestShouldCheckLDAPServerExtensions(t *testing.T) {
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
Password: "password",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -230,7 +230,7 @@ func TestShouldNotEnablePasswdModifyExtension(t *testing.T) {
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
Password: "password",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -286,7 +286,7 @@ func TestShouldReturnCheckServerConnectError(t *testing.T) {
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
Password: "password",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -318,7 +318,7 @@ func TestShouldReturnCheckServerSearchError(t *testing.T) {
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
Password: "password",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -379,7 +379,7 @@ func TestShouldEscapeUserInput(t *testing.T) {
UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
Password: "password",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -414,11 +414,13 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
},
nil,
mockFactory)
assert.True(t, ldapClient.usersFilterReplacementInput)
mockConn.EXPECT().
Search(NewSearchRequestMatcher("(&(uid=john)(&(objectCategory=person)(objectClass=user)))")).
Return(&ldap.SearchResult{}, nil)
@ -456,7 +458,7 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "uid={input}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -486,7 +488,7 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Name: "displayName",
Values: []string{"John Doe"},
},
{
@ -587,7 +589,7 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "uid={input}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -617,7 +619,7 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Name: "displayName",
Values: []string{"John Doe"},
},
{
@ -658,7 +660,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "uid={input}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -716,7 +718,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Name: "displayName",
Values: []string{"John Doe"},
},
{
@ -822,7 +824,7 @@ func TestShouldUpdateUserPasswordActiveDirectory(t *testing.T) {
DN: "cn=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Name: "displayName",
Values: []string{"John Doe"},
},
{
@ -968,7 +970,7 @@ func TestShouldCheckValidUserPassword(t *testing.T) {
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "uid={input}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -991,7 +993,7 @@ func TestShouldCheckValidUserPassword(t *testing.T) {
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Name: "displayName",
Values: []string{"John Doe"},
},
{
@ -1035,7 +1037,7 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) {
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "uid={input}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -1058,7 +1060,7 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) {
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Name: "displayName",
Values: []string{"John Doe"},
},
{
@ -1102,7 +1104,7 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "uid={input}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -1136,7 +1138,7 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Name: "displayName",
Values: []string{"John Doe"},
},
{
@ -1176,7 +1178,7 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "(&(|({username_attribute}={input})({mail_attribute}={input})({display_name_attribute}={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))",
GroupsFilter: "(&(|(member={dn})(member={input})(member={username}))(objectClass=group))",
AdditionalUsersDN: "ou=users",
@ -1187,7 +1189,13 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {
nil,
mockFactory)
assert.Equal(t, "(&(|(uid={input})(mail={input})(displayname={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))", ldapClient.configuration.UsersFilter)
assert.True(t, ldapClient.groupsFilterReplacementInput)
assert.True(t, ldapClient.groupsFilterReplacementUsername)
assert.True(t, ldapClient.groupsFilterReplacementDN)
assert.True(t, ldapClient.usersFilterReplacementInput)
assert.Equal(t, "(&(|(uid={input})(mail={input})(displayName={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))", ldapClient.configuration.UsersFilter)
assert.Equal(t, "(&(|(member={dn})(member={input})(member={username}))(objectClass=group))", ldapClient.configuration.GroupsFilter)
assert.Equal(t, "ou=users,dc=example,dc=com", ldapClient.usersBaseDN)
assert.Equal(t, "ou=groups,dc=example,dc=com", ldapClient.groupsBaseDN)
@ -1207,7 +1215,7 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "uid={input}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",
@ -1219,6 +1227,10 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T
nil,
mockFactory)
assert.False(t, ldapClient.groupsFilterReplacementInput)
assert.False(t, ldapClient.groupsFilterReplacementUsername)
assert.False(t, ldapClient.groupsFilterReplacementDN)
dialURL := mockFactory.EXPECT().
DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).
Return(mockConn, nil)
@ -1244,7 +1256,7 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T
DN: "uid=test,dc=example,dc=com",
Attributes: []*ldap.EntryAttribute{
{
Name: "displayname",
Name: "displayName",
Values: []string{"John Doe"},
},
{
@ -1285,7 +1297,7 @@ func TestShouldReturnLDAPSAlreadySecuredWhenStartTLSAttempted(t *testing.T) {
Password: "password",
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
UsersFilter: "uid={input}",
AdditionalUsersDN: "ou=users",
BaseDN: "dc=example,dc=com",

View File

@ -183,10 +183,8 @@ authentication_backend:
additional_users_dn: ou=users
## The users filter used in search queries to find the user profile based on input filled in login form.
## Various placeholders are available in the user filter:
## - {input} is a placeholder replaced by what the user inputs in the login form.
## - {username_attribute} is a mandatory placeholder replaced by what is configured in `username_attribute`.
## - {mail_attribute} is a placeholder replaced by what is configured in `mail_attribute`.
## Various placeholders are available in the user filter which you can read about in the documentation which can
## be found at: https://www.authelia.com/docs/configuration/authentication/ldap.html#users-filter-replacements
##
## Recommended settings are as follows:
## - Microsoft Active Directory: (&({username_attribute}={input})(objectCategory=person)(objectClass=user))
@ -202,16 +200,13 @@ authentication_backend:
## i.e. with this set to OU=Groups and base_dn set to DC=a,DC=com; OU=Groups,DC=a,DC=com is searched for groups.
additional_groups_dn: ou=groups
## The groups filter used in search queries to find the groups of the user.
## - {input} is a placeholder replaced by what the user inputs in the login form.
## - {username} is a placeholder replace by the username stored in LDAP (based on `username_attribute`).
## - {dn} is a matcher replaced by the user distinguished name, aka, user DN.
## - {username_attribute} is a placeholder replaced by what is configured in `username_attribute`.
## - {mail_attribute} is a placeholder replaced by what is configured in `mail_attribute`.
## The groups filter used in search queries to find the groups based on relevant authenticated user.
## Various placeholders are available in the groups filter which you can read about in the documentation which can
## be found at: https://www.authelia.com/docs/configuration/authentication/ldap.html#groups-filter-replacements
##
## If your groups use the `groupOfUniqueNames` structure use this instead:
## (&(uniquemember={dn})(objectclass=groupOfUniqueNames))
groups_filter: (&(member={dn})(objectclass=groupOfNames))
## (&(uniqueMember={dn})(objectClass=groupOfUniqueNames))
groups_filter: (&(member={dn})(objectClass=groupOfNames))
## The attribute holding the name of the group.
# group_name_attribute: cn
@ -221,7 +216,7 @@ authentication_backend:
# 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
# display_name_attribute: displayName
## The username and password of the admin user.
user: cn=admin,dc=example,dc=com

View File

@ -75,7 +75,7 @@ var DefaultLDAPAuthenticationBackendConfiguration = LDAPAuthenticationBackendCon
Implementation: LDAPImplementationCustom,
UsernameAttribute: "uid",
MailAttribute: "mail",
DisplayNameAttribute: "displayname",
DisplayNameAttribute: "displayName",
GroupNameAttribute: "cn",
TLS: &TLSConfig{
MinimumVersion: "TLS1.2",

View File

@ -23,7 +23,7 @@ authentication_backend:
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))
groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com

View File

@ -23,7 +23,7 @@ authentication_backend:
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))
groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com

View File

@ -24,7 +24,7 @@ authentication_backend:
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))
groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com

View File

@ -25,7 +25,7 @@ authentication_backend:
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))
groups_filter: (&(member={dn})(objectClass=groupOfNames))
group_name_attribute: cn
mail_attribute: mail
user: cn=admin,dc=example,dc=com

View File

@ -387,7 +387,7 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultDisplayNameAttr
suite.Assert().False(suite.validator.HasWarnings())
suite.Assert().False(suite.validator.HasErrors())
suite.Assert().Equal("displayname", suite.configuration.LDAP.DisplayNameAttribute)
suite.Assert().Equal("displayName", suite.configuration.LDAP.DisplayNameAttribute)
}
func (suite *LDAPAuthenticationBackendSuite) TestShouldSetDefaultRefreshInterval() {

View File

@ -25,7 +25,7 @@ authentication_backend:
additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectClass=person))
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

View File

@ -26,7 +26,7 @@ authentication_backend:
additional_users_dn: ou=users
users_filter: (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(objectClass=inetOrgPerson)) # yamllint disable-line rule:line-length
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

View File

@ -1,78 +1,78 @@
dn: cn=pwmanager,{{ LDAP_BASE_DN }}
cn: Password Manager
displayname: Password Manager
displayName: Password Manager
givenName: Password
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: password.manager@authelia.com
sn: Manager
uid: pwmanager
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
dn: ou=groups,{{ LDAP_BASE_DN }}
objectclass: organizationalUnit
objectclass: top
objectClass: organizationalUnit
objectClass: top
ou: groups
dn: ou=users,{{ LDAP_BASE_DN }}
objectclass: organizationalUnit
objectclass: top
objectClass: organizationalUnit
objectClass: top
ou: users
dn: cn=dev,ou=groups,{{ LDAP_BASE_DN }}
cn: dev
member: cn=John Doe (external),ou=users,{{ LDAP_BASE_DN }}
member: cn=Bob Dylan,ou=users,{{ LDAP_BASE_DN }}
objectclass: groupOfNames
objectclass: top
objectClass: groupOfNames
objectClass: top
dn: cn=admins,ou=groups,{{ LDAP_BASE_DN }}
cn: admins
member: cn=John Doe (external),ou=users,{{ LDAP_BASE_DN }}
objectclass: groupOfNames
objectclass: top
objectClass: groupOfNames
objectClass: top
dn: cn=John Doe (external),ou=users,{{ LDAP_BASE_DN }}
cn: John Doe (external)
displayname: John Doe
displayName: John Doe
givenName: John
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: john.doe@authelia.com
sn: Doe
uid: john
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
dn: cn=Harry Potter,ou=users,{{ LDAP_BASE_DN }}
cn: Harry Potter
displayname: Harry Potter
displayName: Harry Potter
givenName: Harry
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: harry.potter@authelia.com
sn: Potter
uid: harry
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
dn: cn=Bob Dylan,ou=users,{{ LDAP_BASE_DN }}
cn: Bob Dylan
displayname: Bob Dylan
displayName: Bob Dylan
givenName: Bob
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: bob.dylan@authelia.com
sn: Dylan
uid: bob
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
dn: cn=James Dean,ou=users,{{ LDAP_BASE_DN }}
cn: James Dean
displayname: James Dean
displayName: James Dean
givenName: James
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: james.dean@authelia.com
sn: Dean
uid: james
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/

View File

@ -24,7 +24,7 @@ authentication_backend:
additional_users_dn: ou=users
users_filter: (&({username_attribute}={input})(objectClass=person))
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

View File

@ -1,67 +1,67 @@
dn: ou=groups,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
objectClass: organizationalUnit
objectClass: top
ou: groups
dn: ou=users,dc=example,dc=com
objectclass: organizationalUnit
objectclass: top
objectClass: organizationalUnit
objectClass: top
ou: users
dn: cn=dev,ou=groups,dc=example,dc=com
cn: dev
member: uid=john,ou=users,dc=example,dc=com
member: uid=bob,ou=users,dc=example,dc=com
objectclass: groupOfNames
objectclass: top
objectClass: groupOfNames
objectClass: top
dn: cn=admins,ou=groups,dc=example,dc=com
cn: admins
member: uid=john,ou=users,dc=example,dc=com
objectclass: groupOfNames
objectclass: top
objectClass: groupOfNames
objectClass: top
dn: uid=john,ou=users,dc=example,dc=com
uid: john
cn: john
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: john.doe@authelia.com
sn: John Doe
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
dn: uid=harry,ou=users,dc=example,dc=com
uid: harry
cn: harry
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: harry.potter@authelia.com
sn: Harry Potter
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
dn: uid=bob,ou=users,dc=example,dc=com
uid: bob
cn: bob
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: bob.dylan@authelia.com
sn: Bob Dylan
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
dn: uid=james,ou=users,dc=example,dc=com
uid: james
cn: james
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: james.dean@authelia.com
sn: James Dean
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
dn: uid=blackhat,ou=users,dc=example,dc=com
uid: blackhat
cn: blackhat
objectclass: inetOrgPerson
objectclass: top
objectClass: inetOrgPerson
objectClass: top
mail: billy.blackhat@authelia.com
sn: Billy BlackHat
userpassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/
userPassword: {CRYPT}$6$rounds=500000$jgiCMRyGXzoqpxS3$w2pJeZnnH8bwW3zzvoMWtTRfQYsHbWbD/hquuQ5vUeIyl9gdwBIt6RWk2S6afBA0DPakbeWgD/4SZPiS0hYtU/

View File

@ -26,6 +26,12 @@ const (
// TLS10 is the textual representation of TLS 1.0.
TLS10 = "1.0"
clean = "clean"
tagged = "tagged"
unknown = "unknown"
)
const (
// Hour is an int based representation of the time unit.
Hour = time.Minute * 60
@ -40,11 +46,9 @@ const (
// Month is an int based representation of the time unit.
Month = Year / 12
)
clean = "clean"
tagged = "tagged"
unknown = "unknown"
const (
errFmtLinuxNotFound = "open %s: no such file or directory"
)