summaryrefslogtreecommitdiff
path: root/internal/authentication/ldap_user_provider_test.go
diff options
context:
space:
mode:
authorJames Elliott <james-d-elliott@users.noreply.github.com>2021-07-02 09:16:16 +1000
committerGitHub <noreply@github.com>2021-07-02 09:16:16 +1000
commitcb71df5d9b541888b0edf56e006340c84e1266c0 (patch)
tree522d56b9bbd6462daa6c311f3727837a2892965c /internal/authentication/ldap_user_provider_test.go
parentf759b27bb054d2e2766ca720123b27e4efea347b (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.go238
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",