diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2021-07-02 09:16:16 +1000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-07-02 09:16:16 +1000 |
| commit | cb71df5d9b541888b0edf56e006340c84e1266c0 (patch) | |
| tree | 522d56b9bbd6462daa6c311f3727837a2892965c /internal/authentication/ldap_user_provider_test.go | |
| parent | f759b27bb054d2e2766ca720123b27e4efea347b (diff) | |
feat(authentiation): check ldap support for extended operations on startup (#2133)
* feat(authentiation): check ldap server on startup
This PR adds a startup check to the LDAP authentication backend. It additionally adds support for checking supportedExtension OIDs, currently only checking passwdModifyOID (1.3.6.1.4.1.4203.1.11.3). This can relatively easily be enhanced to add detection for other rootDSE capabilities like supportedControl and supportedCapabilities as necessary.
* test(authentication): add unit tests for new feature
* refactor(authentication): factorize ldap user provider newup
* refactor: minor adjustments
Diffstat (limited to 'internal/authentication/ldap_user_provider_test.go')
| -rw-r--r-- | internal/authentication/ldap_user_provider_test.go | 238 |
1 files changed, 222 insertions, 16 deletions
diff --git a/internal/authentication/ldap_user_provider_test.go b/internal/authentication/ldap_user_provider_test.go index 5913df0d2..8602bd8b1 100644 --- a/internal/authentication/ldap_user_provider_test.go +++ b/internal/authentication/ldap_user_provider_test.go @@ -2,6 +2,7 @@ package authentication import ( "errors" + "fmt" "testing" "github.com/go-ldap/ldap/v3" @@ -10,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/authelia/authelia/internal/configuration/schema" + "github.com/authelia/authelia/internal/utils" ) func TestShouldCreateRawConnectionWhenSchemeIsLDAP(t *testing.T) { @@ -19,7 +21,7 @@ func TestShouldCreateRawConnectionWhenSchemeIsLDAP(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", }, @@ -46,7 +48,7 @@ func TestShouldCreateTLSConnectionWhenSchemeIsLDAPS(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldaps://127.0.0.1:389", }, @@ -72,7 +74,7 @@ func TestEscapeSpecialCharsFromUserInput(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldaps://127.0.0.1:389", }, @@ -103,7 +105,7 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldaps://127.0.0.1:389", GroupsFilter: "(|(member={dn})(uid={username})(uid={input}))", @@ -125,6 +127,210 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) { assert.Equal(t, "(|(member=cn=john \\28external\\29,dc=example,dc=com)(uid=john)(uid=john\\#\\=\\28abc\\,def\\29))", filter) } +type ExtendedSearchRequestMatcher struct { + filter string + baseDN string + scope int + derefAliases int + typesOnly bool + attributes []string +} + +func NewExtendedSearchRequestMatcher(filter, base string, scope, derefAliases int, typesOnly bool, attributes []string) *ExtendedSearchRequestMatcher { + return &ExtendedSearchRequestMatcher{filter, base, scope, derefAliases, typesOnly, attributes} +} + +func (e *ExtendedSearchRequestMatcher) Matches(x interface{}) bool { + sr := x.(*ldap.SearchRequest) + + if e.filter != sr.Filter || e.baseDN != sr.BaseDN || e.scope != sr.Scope || e.derefAliases != sr.DerefAliases || + e.typesOnly != sr.TypesOnly || utils.IsStringSlicesDifferent(e.attributes, sr.Attributes) { + return false + } + + return true +} + +func (e *ExtendedSearchRequestMatcher) String() string { + return fmt.Sprintf("baseDN: %s, filter %s", e.baseDN, e.filter) +} + +func TestShouldCheckLDAPServerExtensions(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockFactory := NewMockLDAPConnectionFactory(ctrl) + mockConn := NewMockLDAPConnection(ctrl) + + ldapClient := newLDAPUserProvider( + schema.LDAPAuthenticationBackendConfiguration{ + URL: "ldap://127.0.0.1:389", + User: "cn=admin,dc=example,dc=com", + UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", + UsernameAttribute: "uid", + MailAttribute: "mail", + DisplayNameAttribute: "displayname", + Password: "password", + AdditionalUsersDN: "ou=users", + BaseDN: "dc=example,dc=com", + }, + nil, + mockFactory) + + mockFactory.EXPECT(). + DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). + Return(mockConn, nil) + + mockConn.EXPECT(). + Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). + Return(nil) + + mockConn.EXPECT(). + Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute})). + Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: "", + Attributes: []*ldap.EntryAttribute{ + { + Name: ldapSupportedExtensionAttribute, + Values: []string{ldapOIDPasswdModifyExtension}, + }, + }, + }, + }, + }, nil) + + err := ldapClient.checkServer() + assert.NoError(t, err) + + assert.True(t, ldapClient.supportExtensionPasswdModify) +} + +func TestShouldNotEnablePasswdModifyExtension(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockFactory := NewMockLDAPConnectionFactory(ctrl) + mockConn := NewMockLDAPConnection(ctrl) + + ldapClient := newLDAPUserProvider( + schema.LDAPAuthenticationBackendConfiguration{ + URL: "ldap://127.0.0.1:389", + User: "cn=admin,dc=example,dc=com", + UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", + UsernameAttribute: "uid", + MailAttribute: "mail", + DisplayNameAttribute: "displayname", + Password: "password", + AdditionalUsersDN: "ou=users", + BaseDN: "dc=example,dc=com", + }, + nil, + mockFactory) + + mockFactory.EXPECT(). + DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). + Return(mockConn, nil) + + mockConn.EXPECT(). + Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). + Return(nil) + + mockConn.EXPECT(). + Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute})). + Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: "", + Attributes: []*ldap.EntryAttribute{ + { + Name: ldapSupportedExtensionAttribute, + Values: []string{}, + }, + }, + }, + }, + }, nil) + + err := ldapClient.checkServer() + assert.NoError(t, err) + + assert.False(t, ldapClient.supportExtensionPasswdModify) +} + +func TestShouldReturnCheckServerConnectError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockFactory := NewMockLDAPConnectionFactory(ctrl) + mockConn := NewMockLDAPConnection(ctrl) + + ldapClient := newLDAPUserProvider( + schema.LDAPAuthenticationBackendConfiguration{ + URL: "ldap://127.0.0.1:389", + User: "cn=admin,dc=example,dc=com", + UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", + UsernameAttribute: "uid", + MailAttribute: "mail", + DisplayNameAttribute: "displayname", + Password: "password", + AdditionalUsersDN: "ou=users", + BaseDN: "dc=example,dc=com", + }, + nil, + mockFactory) + + mockFactory.EXPECT(). + DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). + Return(mockConn, errors.New("could not connect")) + + err := ldapClient.checkServer() + assert.EqualError(t, err, "could not connect") + + assert.False(t, ldapClient.supportExtensionPasswdModify) +} + +func TestShouldReturnCheckServerSearchError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockFactory := NewMockLDAPConnectionFactory(ctrl) + mockConn := NewMockLDAPConnection(ctrl) + + ldapClient := newLDAPUserProvider( + schema.LDAPAuthenticationBackendConfiguration{ + URL: "ldap://127.0.0.1:389", + User: "cn=admin,dc=example,dc=com", + UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", + UsernameAttribute: "uid", + MailAttribute: "mail", + DisplayNameAttribute: "displayname", + Password: "password", + AdditionalUsersDN: "ou=users", + BaseDN: "dc=example,dc=com", + }, + nil, + mockFactory) + + mockFactory.EXPECT(). + DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). + Return(mockConn, nil) + + mockConn.EXPECT(). + Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). + Return(nil) + + mockConn.EXPECT(). + Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute})). + Return(nil, errors.New("could not perform the search")) + + err := ldapClient.checkServer() + assert.EqualError(t, err, "could not perform the search") + + assert.False(t, ldapClient.supportExtensionPasswdModify) +} + type SearchRequestMatcher struct { expected string } @@ -149,7 +355,7 @@ func TestShouldEscapeUserInput(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -181,7 +387,7 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -226,7 +432,7 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -297,7 +503,7 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -357,7 +563,7 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -428,7 +634,7 @@ func TestShouldUpdateUserPassword(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -495,7 +701,7 @@ func TestShouldCheckValidUserPassword(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -563,7 +769,7 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -631,7 +837,7 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -705,7 +911,7 @@ func TestShouldParseDynamicConfiguration(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -736,7 +942,7 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldap://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", @@ -814,7 +1020,7 @@ func TestShouldReturnLDAPSAlreadySecuredWhenStartTLSAttempted(t *testing.T) { mockFactory := NewMockLDAPConnectionFactory(ctrl) mockConn := NewMockLDAPConnection(ctrl) - ldapClient := NewLDAPUserProviderWithFactory( + ldapClient := newLDAPUserProvider( schema.LDAPAuthenticationBackendConfiguration{ URL: "ldaps://127.0.0.1:389", User: "cn=admin,dc=example,dc=com", |
