feat(authentication): ldap users reset filter

This allows setting a specific users filter for password resets.

Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
feat-ldap-reset-filter
James Elliott 2023-05-11 21:26:14 +10:00
parent 92cf5a186d
commit 099f4fa5e0
No known key found for this signature in database
GPG Key ID: 0F1C4A096E857E49
10 changed files with 97 additions and 42 deletions

View File

@ -421,6 +421,11 @@ authentication_backend:
## (&(|({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))'
## Defaults to the users_filter. This filter is used exclusively for password resets. So you can leverage it to
## prevent users who must change their password from logging in, but if you set a separate filter here they could
## theoretically still reset their password.
# users_reset_filter: '(&({username_attribute}={input})(objectClass=person))'
## The additional_groups_dn is prefixed to base_dn and delimited by a comma when searching for groups. ## The additional_groups_dn is prefixed to base_dn and delimited by a comma when searching for groups.
## 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. ## 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' # additional_groups_dn: 'ou=groups'

View File

@ -209,10 +209,18 @@ exactly which OU to get users from for either security or performance reasons. F
default negating this requirement. Refer to the [filter defaults](../../reference/guides/ldap.md#filter-defaults) for default negating this requirement. Refer to the [filter defaults](../../reference/guides/ldap.md#filter-defaults) for
more information.* more information.*
The LDAP filter to narrow down which users are valid. This is important to set correctly as to exclude disabled users. The LDAP filter to determine users are valid. This is important to set correctly as to exclude disabled users. The
The default value is dependent on the [implementation](#implementation), refer to 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.
### users_reset_filter
{{< confkey type="string" required="no" >}}
The LDAP filter to narrow down which users are valid. This is important to set correctly as to exclude disabled users.
The default value is the same as [users_filter](#usersfilter). This can be leveraged to allow users who cannot login
due to restrictions in the [users_filter](#usersfilter) to still be able to reset their password.
### additional_groups_dn ### additional_groups_dn
{{< confkey type="string" required="no" >}} {{< confkey type="string" required="no" >}}

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,7 @@ import (
"strconv" "strconv"
"strings" "strings"
ldap "github.com/go-ldap/ldap/v3" "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"
@ -106,7 +106,7 @@ func (p *LDAPUserProvider) CheckUserPassword(username string, password string) (
defer client.Close() defer client.Close()
if profile, err = p.getUserProfile(client, username); err != nil { if profile, err = p.getUserProfile(client, username, false); err != nil {
return false, err return false, err
} }
@ -132,7 +132,7 @@ func (p *LDAPUserProvider) GetDetails(username string) (details *UserDetails, er
defer client.Close() defer client.Close()
if profile, err = p.getUserProfile(client, username); err != nil { if profile, err = p.getUserProfile(client, username, false); err != nil {
return nil, err return nil, err
} }
@ -165,7 +165,7 @@ func (p *LDAPUserProvider) UpdatePassword(username, password string) (err error)
defer client.Close() defer client.Close()
if profile, err = p.getUserProfile(client, username); err != nil { if profile, err = p.getUserProfile(client, username, true); err != nil {
return fmt.Errorf("unable to update password. Cause: %w", err) return fmt.Errorf("unable to update password. Cause: %w", err)
} }
@ -306,11 +306,11 @@ func (p *LDAPUserProvider) searchReferrals(request *ldap.SearchRequest, result *
return nil return nil
} }
func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (profile *ldapUserProfile, err error) { func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string, reset bool) (profile *ldapUserProfile, err error) {
// Search for the given username. // Search for the given username.
request := ldap.NewSearchRequest( request := ldap.NewSearchRequest(
p.usersBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, p.usersBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases,
1, 0, false, p.resolveUsersFilter(username), p.usersAttributes, nil, 1, 0, false, p.resolveUsersFilter(username, reset), p.usersAttributes, nil,
) )
p.log. p.log.
@ -518,8 +518,12 @@ func (p *LDAPUserProvider) getUserGroupsRequestMemberOf(client LDAPClient, usern
return groups, nil return groups, nil
} }
func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) { func (p *LDAPUserProvider) resolveUsersFilter(input string, reset bool) (filter string) {
if reset {
filter = p.config.UsersResetFilter
} else {
filter = p.config.UsersFilter filter = p.config.UsersFilter
}
if p.usersFilterReplacementInput { if p.usersFilterReplacementInput {
// The {input} placeholder is replaced by the username input. // The {input} placeholder is replaced by the username input.

View File

@ -91,6 +91,16 @@ func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {
p.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail) 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.config.UsersFilter = strings.ReplaceAll(p.config.UsersFilter, ldapPlaceholderMemberOfAttribute, p.config.Attributes.MemberOf)
if p.config.UsersResetFilter == "" {
p.config.UsersResetFilter = p.config.UsersFilter
} else {
p.config.UsersResetFilter = strings.ReplaceAll(p.config.UsersResetFilter, ldapPlaceholderDistinguishedNameAttribute, p.config.Attributes.DistinguishedName)
p.config.UsersResetFilter = strings.ReplaceAll(p.config.UsersResetFilter, ldapPlaceholderUsernameAttribute, p.config.Attributes.Username)
p.config.UsersResetFilter = strings.ReplaceAll(p.config.UsersResetFilter, ldapPlaceholderDisplayNameAttribute, p.config.Attributes.DisplayName)
p.config.UsersResetFilter = strings.ReplaceAll(p.config.UsersResetFilter, ldapPlaceholderMailAttribute, p.config.Attributes.Mail)
p.config.UsersResetFilter = strings.ReplaceAll(p.config.UsersResetFilter, 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 len(p.config.Attributes.Username) != 0 && !utils.IsStringInSlice(p.config.Attributes.Username, p.usersAttributes) { if len(p.config.Attributes.Username) != 0 && !utils.IsStringInSlice(p.config.Attributes.Username, p.usersAttributes) {

View File

@ -6,7 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/go-ldap/ldap/v3" ldap "github.com/go-ldap/ldap/v3"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -757,7 +757,7 @@ func TestShouldEscapeUserInput(t *testing.T) {
Search(NewSearchRequestMatcher("(|(uid=john\\=abc)(mail=john\\=abc))")). Search(NewSearchRequestMatcher("(|(uid=john\\=abc)(mail=john\\=abc))")).
Return(&ldap.SearchResult{}, nil) Return(&ldap.SearchResult{}, nil)
_, err := provider.getUserProfile(mockClient, "john=abc") _, err := provider.getUserProfile(mockClient, "john=abc", false)
require.Error(t, err) require.Error(t, err)
assert.EqualError(t, err, "user not found") assert.EqualError(t, err, "user not found")
} }
@ -823,7 +823,7 @@ func TestShouldReturnEmailWhenAttributeSameAsUsername(t *testing.T) {
client, err := provider.connect() client, err := provider.connect()
assert.NoError(t, err) assert.NoError(t, err)
profile, err := provider.getUserProfile(client, "john@example.com") profile, err := provider.getUserProfile(client, "john@example.com", false)
assert.NoError(t, err) assert.NoError(t, err)
require.NotNil(t, profile) require.NotNil(t, profile)
@ -897,7 +897,7 @@ func TestShouldReturnUsernameAndBlankDisplayNameWhenAttributesTheSame(t *testing
client, err := provider.connect() client, err := provider.connect()
assert.NoError(t, err) assert.NoError(t, err)
profile, err := provider.getUserProfile(client, "john@example.com") profile, err := provider.getUserProfile(client, "john@example.com", false)
assert.NoError(t, err) assert.NoError(t, err)
require.NotNil(t, profile) require.NotNil(t, profile)
@ -975,7 +975,7 @@ func TestShouldReturnBlankEmailAndDisplayNameWhenAttrsLenZero(t *testing.T) {
client, err := provider.connect() client, err := provider.connect()
assert.NoError(t, err) assert.NoError(t, err)
profile, err := provider.getUserProfile(client, "john@example.com") profile, err := provider.getUserProfile(client, "john@example.com", false)
assert.NoError(t, err) assert.NoError(t, err)
require.NotNil(t, profile) require.NotNil(t, profile)
@ -1022,7 +1022,7 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {
Search(NewSearchRequestMatcher("(&(uid=john)(&(objectCategory=person)(objectClass=user)))")). Search(NewSearchRequestMatcher("(&(uid=john)(&(objectCategory=person)(objectClass=user)))")).
Return(&ldap.SearchResult{}, nil) Return(&ldap.SearchResult{}, nil)
_, err := provider.getUserProfile(mockClient, "john") _, err := provider.getUserProfile(mockClient, "john", false)
require.Error(t, err) require.Error(t, err)
assert.EqualError(t, err, "user not found") assert.EqualError(t, err, "user not found")
} }
@ -3975,7 +3975,7 @@ func TestShouldReturnErrorWhenMultipleUsernameAttributes(t *testing.T) {
client, err := provider.connect() client, err := provider.connect()
assert.NoError(t, err) assert.NoError(t, err)
profile, err := provider.getUserProfile(client, "john") profile, err := provider.getUserProfile(client, "john", false)
assert.Nil(t, profile) assert.Nil(t, profile)
assert.EqualError(t, err, "user 'john' has 2 values for for attribute 'uid' but the attribute must be a single value attribute") assert.EqualError(t, err, "user 'john' has 2 values for for attribute 'uid' but the attribute must be a single value attribute")
@ -4044,7 +4044,7 @@ func TestShouldReturnErrorWhenZeroUsernameAttributes(t *testing.T) {
client, err := provider.connect() client, err := provider.connect()
assert.NoError(t, err) assert.NoError(t, err)
profile, err := provider.getUserProfile(client, "john") profile, err := provider.getUserProfile(client, "john", false)
assert.Nil(t, profile) assert.Nil(t, profile)
assert.EqualError(t, err, "user 'john' must have value for attribute 'uid'") assert.EqualError(t, err, "user 'john' must have value for attribute 'uid'")
@ -4109,7 +4109,7 @@ func TestShouldReturnErrorWhenUsernameAttributeNotReturned(t *testing.T) {
client, err := provider.connect() client, err := provider.connect()
assert.NoError(t, err) assert.NoError(t, err)
profile, err := provider.getUserProfile(client, "john") profile, err := provider.getUserProfile(client, "john", false)
assert.Nil(t, profile) assert.Nil(t, profile)
assert.EqualError(t, err, "user 'john' must have value for attribute 'uid'") assert.EqualError(t, err, "user 'john' must have value for attribute 'uid'")
@ -4195,7 +4195,7 @@ func TestShouldReturnErrorWhenMultipleUsersFound(t *testing.T) {
client, err := provider.connect() client, err := provider.connect()
assert.NoError(t, err) assert.NoError(t, err)
profile, err := provider.getUserProfile(client, "john") profile, err := provider.getUserProfile(client, "john", false)
assert.Nil(t, profile) assert.Nil(t, profile)
assert.EqualError(t, err, "there were 2 users found when searching for 'john' but there should only be 1") assert.EqualError(t, err, "there were 2 users found when searching for 'john' but there should only be 1")
@ -4264,7 +4264,7 @@ func TestShouldReturnErrorWhenNoDN(t *testing.T) {
client, err := provider.connect() client, err := provider.connect()
assert.NoError(t, err) assert.NoError(t, err)
profile, err := provider.getUserProfile(client, "john") profile, err := provider.getUserProfile(client, "john", false)
assert.Nil(t, profile) assert.Nil(t, profile)
assert.EqualError(t, err, "user 'john' must have a distinguished name but the result returned an empty distinguished name") assert.EqualError(t, err, "user 'john' must have a distinguished name but the result returned an empty distinguished name")
@ -4581,7 +4581,7 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {
assert.Equal(t, "ou=users,dc=example,dc=com", provider.usersBaseDN) assert.Equal(t, "ou=users,dc=example,dc=com", provider.usersBaseDN)
assert.Equal(t, "ou=groups,dc=example,dc=com", provider.groupsBaseDN) assert.Equal(t, "ou=groups,dc=example,dc=com", provider.groupsBaseDN)
assert.Equal(t, "(&(|(uid=test@example.com)(mail=test@example.com))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>=133147241190000000)(accountExpires>=20221205142839.0Z)))", provider.resolveUsersFilter("test@example.com")) assert.Equal(t, "(&(|(uid=test@example.com)(mail=test@example.com))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>=133147241190000000)(accountExpires>=20221205142839.0Z)))", provider.resolveUsersFilter("test@example.com", false))
assert.Equal(t, "(&(|(member=cn=admin,dc=example,dc=com)(member=test@example.com)(member=test))(objectClass=group))", provider.resolveGroupsFilter("test@example.com", &ldapUserProfile{Username: "test", DN: "cn=admin,dc=example,dc=com"})) assert.Equal(t, "(&(|(member=cn=admin,dc=example,dc=com)(member=test@example.com)(member=test))(objectClass=group))", provider.resolveGroupsFilter("test@example.com", &ldapUserProfile{Username: "test", DN: "cn=admin,dc=example,dc=com"}))
} }

View File

@ -421,6 +421,11 @@ authentication_backend:
## (&(|({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))'
## Defaults to the users_filter. This filter is used exclusively for password resets. So you can leverage it to
## prevent users who must change their password from logging in, but if you set a separate filter here they could
## theoretically still reset their password.
# users_reset_filter: '(&({username_attribute}={input})(objectClass=person))'
## The additional_groups_dn is prefixed to base_dn and delimited by a comma when searching for groups. ## The additional_groups_dn is prefixed to base_dn and delimited by a comma when searching for groups.
## 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. ## 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' # additional_groups_dn: 'ou=groups'

View File

@ -105,6 +105,7 @@ type LDAPAuthenticationBackend struct {
AdditionalUsersDN string `koanf:"additional_users_dn"` AdditionalUsersDN string `koanf:"additional_users_dn"`
UsersFilter string `koanf:"users_filter"` UsersFilter string `koanf:"users_filter"`
UsersResetFilter string `koanf:"users_reset_filter"`
AdditionalGroupsDN string `koanf:"additional_groups_dn"` AdditionalGroupsDN string `koanf:"additional_groups_dn"`
GroupsFilter string `koanf:"groups_filter"` GroupsFilter string `koanf:"groups_filter"`

View File

@ -116,6 +116,7 @@ var Keys = []string{
"authentication_backend.ldap.base_dn", "authentication_backend.ldap.base_dn",
"authentication_backend.ldap.additional_users_dn", "authentication_backend.ldap.additional_users_dn",
"authentication_backend.ldap.users_filter", "authentication_backend.ldap.users_filter",
"authentication_backend.ldap.users_reset_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_search_mode", "authentication_backend.ldap.group_search_mode",

View File

@ -479,30 +479,45 @@ func validateLDAPRequiredParameters(config *schema.AuthenticationBackend, valida
if config.LDAP.UsersFilter == "" { if config.LDAP.UsersFilter == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "users_filter")) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "users_filter"))
} else { } else {
if !strings.HasPrefix(config.LDAP.UsersFilter, "(") || !strings.HasSuffix(config.LDAP.UsersFilter, ")") { validateLDAPUsersFilter(config, "users_filter", config.LDAP.UsersFilter, validator)
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, "users_filter", config.LDAP.UsersFilter, config.LDAP.UsersFilter))
} }
if !strings.Contains(config.LDAP.UsersFilter, "{username_attribute}") { if config.LDAP.UsersResetFilter != "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "username_attribute")) validateLDAPUsersFilter(config, "users_reset_filter", config.LDAP.UsersFilter, validator)
} }
// This test helps the user know that users_filter is broken after the breaking change induced by this commit. validateLDAPGroupFilter(config, validator)
if !strings.Contains(config.LDAP.UsersFilter, "{input}") { }
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, "users_filter", "input"))
} func validateLDAPUsersFilter(config *schema.AuthenticationBackend, name, filter string, val *schema.StructValidator) {
if !strings.HasPrefix(filter, "(") || !strings.HasSuffix(filter, ")") {
val.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterEnclosingParenthesis, name, filter, filter))
} }
if !strings.Contains(filter, "{username_attribute}") {
val.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, name, "username_attribute"))
}
if !strings.Contains(filter, "{input}") {
val.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingPlaceholder, name, "input"))
}
if config.LDAP.Attributes.DistinguishedName == "" && strings.Contains(filter, "{distinguished_name_attribute}") {
val.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "distinguished_name", strJoinOr([]string{"{distinguished_name_attribute}"})))
}
if config.LDAP.Attributes.MemberOf == "" && strings.Contains(filter, "{member_of_attribute}") {
val.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "member_of", strJoinOr([]string{"{member_of_attribute}"})))
}
}
func validateLDAPGroupFilter(config *schema.AuthenticationBackend, validator *schema.StructValidator) {
if config.LDAP.GroupsFilter == "" { if config.LDAP.GroupsFilter == "" {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "groups_filter")) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendMissingOption, "groups_filter"))
} 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 == "" { if config.LDAP.GroupSearchMode == "" {
config.LDAP.GroupSearchMode = schema.LDAPGroupSearchModeFilter config.LDAP.GroupSearchMode = schema.LDAPGroupSearchModeFilter
} }
@ -511,7 +526,7 @@ func validateLDAPGroupFilter(config *schema.AuthenticationBackend, validator *sc
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendOptionMustBeOneOf, "group_search_mode", strJoinOr(validLDAPGroupSearchModes), config.LDAP.GroupSearchMode)) 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}") pMemberOf, pMemberOfDN, pMemberOfRDN := strings.Contains(config.LDAP.GroupsFilter, "{member_of_attribute}"), strings.Contains(config.LDAP.GroupsFilter, "{memberof:dn}"), strings.Contains(config.LDAP.GroupsFilter, "{memberof:rdn}")
if config.LDAP.GroupSearchMode == schema.LDAPGroupSearchModeMemberOf { if config.LDAP.GroupSearchMode == schema.LDAPGroupSearchModeMemberOf {
if !pMemberOfDN && !pMemberOfRDN { if !pMemberOfDN && !pMemberOfRDN {
@ -523,7 +538,13 @@ func validateLDAPGroupFilter(config *schema.AuthenticationBackend, validator *sc
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "distinguished_name", strJoinOr([]string{"{memberof:dn}"}))) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "distinguished_name", strJoinOr([]string{"{memberof:dn}"})))
} }
if (pMemberOfDN || pMemberOfRDN) && config.LDAP.Attributes.MemberOf == "" { if config.LDAP.Attributes.MemberOf == "" {
if pMemberOfDN || pMemberOfRDN {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "member_of", strJoinOr([]string{"{memberof:rdn}", "{memberof:dn}"}))) validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "member_of", strJoinOr([]string{"{memberof:rdn}", "{memberof:dn}"})))
} }
if pMemberOf {
validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterMissingAttribute, "member_of", strJoinOr([]string{"{member_of_attribute}"})))
}
}
} }