diff options
| author | James Elliott <james-d-elliott@users.noreply.github.com> | 2025-02-23 19:05:57 +1100 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-02-23 08:05:57 +0000 | 
| commit | 0af038e0ced689db90da480876a0bb26d78c6fb9 (patch) | |
| tree | 5d97fe07636fcc5f7c6d87d6535bc5e1f0a9f2eb | |
| parent | 197b45521f5e3799d0b9ef1ec0000d4f83abdee9 (diff) | |
feat(authentication): ldap connection pooling (#7217)
This implements optional LDAP connection pooling to optimize the speed of LDAP transactions.
Signed-off-by: James Elliott <james-d-elliott@users.noreply.github.com>
34 files changed, 3276 insertions, 2313 deletions
diff --git a/config.template.yml b/config.template.yml index a3181081d..2e01b7a15 100644 --- a/config.template.yml +++ b/config.template.yml @@ -459,6 +459,7 @@ identity_validation:      ## Use StartTLS with the LDAP connection.      # start_tls: false +    ## TLS configuration.      # tls:        ## The server subject name to check the servers certificate against during the validation process.        ## This option is not required if the certificate has a SAN which matches the address options hostname. @@ -495,6 +496,20 @@ identity_validation:          # ...          # -----END RSA PRIVATE KEY----- +    ## Connection Pooling configuration. +    # pooling: +      ## Enable Pooling. +      # enable: false + +      ## Pool count. +      # count: 5 + +      ## Retries to obtain a connection during the timeout. +      # retries: 2 + +      ## Timeout before the attempt to obtain a connection fails. +      # timeout: '10 seconds' +      ## The distinguished name of the container searched for objects in the directory information tree.      ## See also: additional_users_dn, additional_groups_dn.      # base_dn: 'dc=example,dc=com' diff --git a/docs/content/configuration/first-factor/ldap.md b/docs/content/configuration/first-factor/ldap.md index 3d2043f5f..6730e302c 100644 --- a/docs/content/configuration/first-factor/ldap.md +++ b/docs/content/configuration/first-factor/ldap.md @@ -50,6 +50,11 @@ authentication_backend:          -----BEGIN RSA PRIVATE KEY-----          ...          -----END RSA PRIVATE KEY----- +    pooling: +      enable: false +      count: 5 +      retries: 2 +      timeout: '10 seconds'      base_dn: '{{< sitevar name="domain" format="dn" nojs="DC=example,DC=com" >}}'      additional_users_dn: 'OU=users'      users_filter: '(&({username_attribute}={input})(objectClass=person))' @@ -155,6 +160,35 @@ By default Authelia uses the system certificate trust for TLS certificate verifi  this. +### pooling + +The connection pooling configuration. + +#### enable + +{{< confkey type="boolean" default="false" required="no" >}} + +Enables the connection pooling functionality. + +#### count + +{{< confkey type="integer" default="5" required="no" >}} + +The number of open connections to be available in the pool at any given time. + +#### retries + +{{< confkey type="integer" default="2" required="no" >}} + +The number of attempts to obtain a free connecting that are made within the timeout period. This effectively splits the +timeout into chunks. + +#### timeout + +{{< confkey type="string,integer" syntax="duration" default="5 seconds" required="no" >}} + +The amount of time that we wait for a connection to become free in the pool before giving up and failing with an error. +  ### base_dn  {{< confkey type="string" required="situational" >}} diff --git a/docs/content/integration/openid-connect/stirling-pdf/index.md b/docs/content/integration/openid-connect/stirling-pdf/index.md index 36c5ae29b..912c95391 100644 --- a/docs/content/integration/openid-connect/stirling-pdf/index.md +++ b/docs/content/integration/openid-connect/stirling-pdf/index.md @@ -2,7 +2,7 @@  title: "Stirling-PDF"  description: "Integrating Stirling-PDF with the Authelia OpenID Connect 1.0 Provider."  summary: "" -date: 2025-02-23T17:51:47+10:00 +date: 2025-02-23T04:38:52+00:00  draft: false  images: []  weight: 620 diff --git a/docs/content/reference/guides/authentication-method-references.md b/docs/content/reference/guides/authentication-method-references.md index 6470e680e..c00d35fdc 100644 --- a/docs/content/reference/guides/authentication-method-references.md +++ b/docs/content/reference/guides/authentication-method-references.md @@ -2,7 +2,7 @@  title: "Authentication Method Reference Values"  description: "This guide shows a list of Authentication Method Reference Values based on RFC8176 and how they are implemented within Authelia"  summary: "This guide shows a list of other frequently asked question documents as well as some general ones." -date: 2025-01-27T09:23:06+11:00 +date: 2025-02-23T16:08:49+11:00  draft: false  images: []  weight: 220 diff --git a/docs/content/reference/guides/webauthn.md b/docs/content/reference/guides/webauthn.md index 71e8d9e57..2ff01c566 100644 --- a/docs/content/reference/guides/webauthn.md +++ b/docs/content/reference/guides/webauthn.md @@ -2,7 +2,7 @@  title: "WebAuthn"  description: "A reference guide on various WebAuthn features and topics"  summary: "This section contains reference documentation for Authelia's WebAuthn implementation and capabilities." -date: 2025-01-27T09:23:06+11:00 +date: 2025-02-23T16:08:49+11:00  draft: false  images: []  weight: 220 diff --git a/docs/data/configkeys.json b/docs/data/configkeys.json index 9f3f8de57..a93d83920 100644 --- a/docs/data/configkeys.json +++ b/docs/data/configkeys.json @@ -305,6 +305,26 @@          "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_TLS_CERTIFICATE_CHAIN_FILE"      },      { +        "path": "authentication_backend.ldap.pooling.enable", +        "secret": false, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_ENABLE" +    }, +    { +        "path": "authentication_backend.ldap.pooling.count", +        "secret": false, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_COUNT" +    }, +    { +        "path": "authentication_backend.ldap.pooling.retries", +        "secret": false, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_RETRIES" +    }, +    { +        "path": "authentication_backend.ldap.pooling.timeout", +        "secret": false, +        "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_POOLING_TIMEOUT" +    }, +    {          "path": "authentication_backend.ldap.base_dn",          "secret": false,          "env": "AUTHELIA_AUTHENTICATION_BACKEND_LDAP_BASE_DN" diff --git a/docs/static/schemas/latest/json-schema/configuration.json b/docs/static/schemas/latest/json-schema/configuration.json index 0bbeac431..a6d722ba3 100644 --- a/docs/static/schemas/latest/json-schema/configuration.json +++ b/docs/static/schemas/latest/json-schema/configuration.json @@ -713,6 +713,11 @@            "title": "TLS",            "description": "The LDAP directory server TLS connection properties."          }, +        "pooling": { +          "$ref": "#/$defs/AuthenticationBackendLDAPPooling", +          "title": "Pooling", +          "description": "The LDAP Connection Pooling properties." +        },          "base_dn": {            "type": "string",            "title": "Base DN", @@ -821,6 +826,44 @@        "type": "object",        "description": "AuthenticationBackendLDAPAttributes represents the configuration related to LDAP server attributes."      }, +    "AuthenticationBackendLDAPPooling": { +      "properties": { +        "enable": { +          "type": "boolean", +          "title": "Enable", +          "description": "Enable LDAP connection pooling.", +          "default": false +        }, +        "count": { +          "type": "integer", +          "title": "Count", +          "description": "The number of connections to keep open for LDAP connection pooling.", +          "default": 5 +        }, +        "retries": { +          "type": "integer", +          "title": "Retries", +          "description": "The number of attempts to retrieve a connection from the pool during the timeout.", +          "default": 2 +        }, +        "timeout": { +          "oneOf": [ +            { +              "type": "string", +              "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$" +            }, +            { +              "type": "integer", +              "description": "The duration in seconds" +            } +          ], +          "title": "Timeout", +          "description": "The duration of time to wait for a connection to become available in the connection pool." +        } +      }, +      "additionalProperties": false, +      "type": "object" +    },      "AuthenticationBackendPasswordReset": {        "properties": {          "disable": { diff --git a/docs/static/schemas/v4.38/json-schema/configuration.json b/docs/static/schemas/v4.38/json-schema/configuration.json index 0bbeac431..a6d722ba3 100644 --- a/docs/static/schemas/v4.38/json-schema/configuration.json +++ b/docs/static/schemas/v4.38/json-schema/configuration.json @@ -713,6 +713,11 @@            "title": "TLS",            "description": "The LDAP directory server TLS connection properties."          }, +        "pooling": { +          "$ref": "#/$defs/AuthenticationBackendLDAPPooling", +          "title": "Pooling", +          "description": "The LDAP Connection Pooling properties." +        },          "base_dn": {            "type": "string",            "title": "Base DN", @@ -821,6 +826,44 @@        "type": "object",        "description": "AuthenticationBackendLDAPAttributes represents the configuration related to LDAP server attributes."      }, +    "AuthenticationBackendLDAPPooling": { +      "properties": { +        "enable": { +          "type": "boolean", +          "title": "Enable", +          "description": "Enable LDAP connection pooling.", +          "default": false +        }, +        "count": { +          "type": "integer", +          "title": "Count", +          "description": "The number of connections to keep open for LDAP connection pooling.", +          "default": 5 +        }, +        "retries": { +          "type": "integer", +          "title": "Retries", +          "description": "The number of attempts to retrieve a connection from the pool during the timeout.", +          "default": 2 +        }, +        "timeout": { +          "oneOf": [ +            { +              "type": "string", +              "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$" +            }, +            { +              "type": "integer", +              "description": "The duration in seconds" +            } +          ], +          "title": "Timeout", +          "description": "The duration of time to wait for a connection to become available in the connection pool." +        } +      }, +      "additionalProperties": false, +      "type": "object" +    },      "AuthenticationBackendPasswordReset": {        "properties": {          "disable": { diff --git a/docs/static/schemas/v4.39/json-schema/configuration.json b/docs/static/schemas/v4.39/json-schema/configuration.json index 221ec38d6..39d9ad7d4 100644 --- a/docs/static/schemas/v4.39/json-schema/configuration.json +++ b/docs/static/schemas/v4.39/json-schema/configuration.json @@ -734,6 +734,11 @@            "title": "TLS",            "description": "The LDAP directory server TLS connection properties."          }, +        "pooling": { +          "$ref": "#/$defs/AuthenticationBackendLDAPPooling", +          "title": "Pooling", +          "description": "The LDAP Connection Pooling properties." +        },          "base_dn": {            "type": "string",            "title": "Base DN", @@ -968,6 +973,44 @@        "additionalProperties": false,        "type": "object"      }, +    "AuthenticationBackendLDAPPooling": { +      "properties": { +        "enable": { +          "type": "boolean", +          "title": "Enable", +          "description": "Enable LDAP connection pooling.", +          "default": false +        }, +        "count": { +          "type": "integer", +          "title": "Count", +          "description": "The number of connections to keep open for LDAP connection pooling.", +          "default": 5 +        }, +        "retries": { +          "type": "integer", +          "title": "Retries", +          "description": "The number of attempts to retrieve a connection from the pool during the timeout.", +          "default": 2 +        }, +        "timeout": { +          "oneOf": [ +            { +              "type": "string", +              "pattern": "^\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?))(\\s*\\d+\\s*(y|M|w|d|h|m|s|ms|((year|month|week|day|hour|minute|second|millisecond)s?)))*$" +            }, +            { +              "type": "integer", +              "description": "The duration in seconds" +            } +          ], +          "title": "Timeout", +          "description": "The duration of time to wait for a connection to become available in the connection pool." +        } +      }, +      "additionalProperties": false, +      "type": "object" +    },      "AuthenticationBackendPasswordReset": {        "properties": {          "disable": { diff --git a/internal/authentication/file_user_provider.go b/internal/authentication/file_user_provider.go index 02e0faa81..0dd84e085 100644 --- a/internal/authentication/file_user_provider.go +++ b/internal/authentication/file_user_provider.go @@ -25,7 +25,7 @@ type FileUserProvider struct {  	config        *schema.AuthenticationBackendFile  	hash          algorithm.Hash  	database      FileUserProviderDatabase -	mutex         *sync.Mutex +	mutex         sync.Mutex  	timeoutReload time.Time  } @@ -33,7 +33,6 @@ type FileUserProvider struct {  func NewFileUserProvider(config *schema.AuthenticationBackendFile) (provider *FileUserProvider) {  	return &FileUserProvider{  		config:        config, -		mutex:         &sync.Mutex{},  		timeoutReload: time.Now().Add(-1 * time.Second),  		database:      NewFileUserDatabase(config.Path, config.Search.Email, config.Search.CaseInsensitive, getExtra(config)),  	} @@ -77,6 +76,10 @@ func (p *FileUserProvider) Reload() (reloaded bool, err error) {  	return true, nil  } +func (p *FileUserProvider) Shutdown() (err error) { +	return nil +} +  // CheckUserPassword checks if provided password matches for the given user.  func (p *FileUserProvider) CheckUserPassword(username string, password string) (match bool, err error) {  	var details FileUserDatabaseUserDetails diff --git a/internal/authentication/file_user_provider_test.go b/internal/authentication/file_user_provider_test.go index 1da33a389..0925d893f 100644 --- a/internal/authentication/file_user_provider_test.go +++ b/internal/authentication/file_user_provider_test.go @@ -86,7 +86,6 @@ func TestShouldNotPanicOnNilDB(t *testing.T) {  	provider := &FileUserProvider{  		config:        &schema.AuthenticationBackendFile{Path: f, Password: schema.DefaultPasswordConfig}, -		mutex:         &sync.Mutex{},  		timeoutReload: time.Now().Add(-1 * time.Second),  	} @@ -102,7 +101,7 @@ func TestShouldHandleBadConfig(t *testing.T) {  	provider := &FileUserProvider{  		config:        &schema.AuthenticationBackendFile{Path: f, Password: schema.DefaultPasswordConfig, ExtraAttributes: map[string]schema.AuthenticationBackendExtraAttribute{"example": {ValueType: "integer"}}}, -		mutex:         &sync.Mutex{}, +		mutex:         sync.Mutex{},  		timeoutReload: time.Now().Add(-1 * time.Second),  	} diff --git a/internal/authentication/gen.go b/internal/authentication/gen.go index 3e92fd178..c908f02d7 100644 --- a/internal/authentication/gen.go +++ b/internal/authentication/gen.go @@ -3,7 +3,8 @@ package authentication  // This file is used to generate mocks. You can generate all mocks using the  // command `go generate github.com/authelia/authelia/v4/internal/authentication`. -//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_mock_test.go -mock_names Client=MockLDAPClient github.com/go-ldap/ldap/v3 Client +//go:generate mockgen -package authentication -destination ldap_client_dialer_test.go -mock_names LDAPClientDialer=MockLDAPClientDialer github.com/authelia/authelia/v4/internal/authentication LDAPClientDialer  //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 FileUserProviderDatabase=MockFileUserDatabase github.com/authelia/authelia/v4/internal/authentication FileUserProviderDatabase  //go:generate mockgen -package authentication -destination file_user_provider_hash_mock_test.go -mock_names Hash=MockHash github.com/go-crypt/crypt/algorithm Hash diff --git a/internal/authentication/ldap_client_dialer.go b/internal/authentication/ldap_client_dialer.go new file mode 100644 index 000000000..4577e2eae --- /dev/null +++ b/internal/authentication/ldap_client_dialer.go @@ -0,0 +1,31 @@ +package authentication + +import ( +	"fmt" + +	"github.com/go-ldap/ldap/v3" +) + +// LDAPClientDialer is an abstract type that dials a ldap.Client. +type LDAPClientDialer interface { +	// DialURL takes a single address and dials it returning the ldap.Client. +	DialURL(addr string, opts ...ldap.DialOpt) (client ldap.Client, err error) +} + +// NewLDAPClientDialerStandard returns a new *LDAPClientDialerStandard. +func NewLDAPClientDialerStandard() *LDAPClientDialerStandard { +	return &LDAPClientDialerStandard{} +} + +// LDAPClientDialerStandard is a concrete type that dials a ldap.Client and returns it, implementing the +// LDAPClientDialer. +type LDAPClientDialerStandard struct{} + +// DialURL takes a single address and dials it returning the ldap.Client. +func (d *LDAPClientDialerStandard) DialURL(addr string, opts ...ldap.DialOpt) (client ldap.Client, err error) { +	if client, err = ldap.DialURL(addr, opts...); err != nil { +		return nil, fmt.Errorf("failed to dial LDAP server at %s: %w", addr, err) +	} + +	return client, nil +} diff --git a/internal/authentication/ldap_client_dialer_test.go b/internal/authentication/ldap_client_dialer_test.go new file mode 100644 index 000000000..40228a8ea --- /dev/null +++ b/internal/authentication/ldap_client_dialer_test.go @@ -0,0 +1,61 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/authelia/authelia/v4/internal/authentication (interfaces: LDAPClientDialer) +// +// Generated by this command: +// +//	mockgen -package authentication -destination ldap_client_dialer_test.go -mock_names LDAPClientDialer=MockLDAPClientDialer github.com/authelia/authelia/v4/internal/authentication LDAPClientDialer +// + +// Package authentication is a generated GoMock package. +package authentication + +import ( +	reflect "reflect" + +	ldap "github.com/go-ldap/ldap/v3" +	gomock "go.uber.org/mock/gomock" +) + +// MockLDAPClientDialer is a mock of LDAPClientDialer interface. +type MockLDAPClientDialer struct { +	ctrl     *gomock.Controller +	recorder *MockLDAPClientDialerMockRecorder +	isgomock struct{} +} + +// MockLDAPClientDialerMockRecorder is the mock recorder for MockLDAPClientDialer. +type MockLDAPClientDialerMockRecorder struct { +	mock *MockLDAPClientDialer +} + +// NewMockLDAPClientDialer creates a new mock instance. +func NewMockLDAPClientDialer(ctrl *gomock.Controller) *MockLDAPClientDialer { +	mock := &MockLDAPClientDialer{ctrl: ctrl} +	mock.recorder = &MockLDAPClientDialerMockRecorder{mock} +	return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLDAPClientDialer) EXPECT() *MockLDAPClientDialerMockRecorder { +	return m.recorder +} + +// DialURL mocks base method. +func (m *MockLDAPClientDialer) DialURL(addr string, opts ...ldap.DialOpt) (ldap.Client, error) { +	m.ctrl.T.Helper() +	varargs := []any{addr} +	for _, a := range opts { +		varargs = append(varargs, a) +	} +	ret := m.ctrl.Call(m, "DialURL", varargs...) +	ret0, _ := ret[0].(ldap.Client) +	ret1, _ := ret[1].(error) +	return ret0, ret1 +} + +// DialURL indicates an expected call of DialURL. +func (mr *MockLDAPClientDialerMockRecorder) DialURL(addr any, opts ...any) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	varargs := append([]any{addr}, opts...) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialURL", reflect.TypeOf((*MockLDAPClientDialer)(nil).DialURL), varargs...) +} diff --git a/internal/authentication/ldap_client_factory.go b/internal/authentication/ldap_client_factory.go index d360f05b3..9bb55502c 100644 --- a/internal/authentication/ldap_client_factory.go +++ b/internal/authentication/ldap_client_factory.go @@ -1,18 +1,314 @@  package authentication  import ( +	"context" +	"crypto/tls" +	"crypto/x509" +	"errors" +	"fmt" +	"net" +	"sync" +	"time" +  	"github.com/go-ldap/ldap/v3" + +	"github.com/authelia/authelia/v4/internal/configuration/schema" +	"github.com/authelia/authelia/v4/internal/utils"  ) -// ProductionLDAPClientFactory the production implementation of an ldap connection factory. -type ProductionLDAPClientFactory struct{} +// LDAPClientFactory an interface describing factories that produce LDAPConnection implementations. +type LDAPClientFactory interface { +	Initialize() (err error) +	GetClient(opts ...LDAPClientFactoryOption) (client ldap.Client, err error) +	ReleaseClient(client ldap.Client) (err error) +	Close() (err error) +} + +// NewStandardLDAPClientFactory create a concrete ldap connection factory. +func NewStandardLDAPClientFactory(config *schema.AuthenticationBackendLDAP, certs *x509.CertPool, dialer LDAPClientDialer) LDAPClientFactory { +	if dialer == nil { +		dialer = &LDAPClientDialerStandard{} +	} + +	tlsc := utils.NewTLSConfig(config.TLS, certs) + +	opts := []ldap.DialOpt{ +		ldap.DialWithDialer(&net.Dialer{Timeout: config.Timeout}), +		ldap.DialWithTLSConfig(tlsc), +	} + +	return &StandardLDAPClientFactory{ +		config: config, +		tls:    tlsc, +		opts:   opts, +		dialer: dialer, +	} +} + +// StandardLDAPClientFactory the production implementation of an ldap connection factory. +type StandardLDAPClientFactory struct { +	config *schema.AuthenticationBackendLDAP +	tls    *tls.Config +	opts   []ldap.DialOpt +	dialer LDAPClientDialer +} + +func (f *StandardLDAPClientFactory) Initialize() (err error) { +	return nil +} + +func (f *StandardLDAPClientFactory) GetClient(opts ...LDAPClientFactoryOption) (client ldap.Client, err error) { +	return getLDAPClient(f.config.Address.String(), f.config.User, f.config.Password, f.dialer, f.tls, f.config.StartTLS, f.opts, opts...) +} + +func (f *StandardLDAPClientFactory) ReleaseClient(client ldap.Client) (err error) { +	if err = client.Close(); err != nil { +		return fmt.Errorf("error occurred closing LDAP client: %w", err) +	} + +	return nil +} -// NewProductionLDAPClientFactory create a concrete ldap connection factory. -func NewProductionLDAPClientFactory() *ProductionLDAPClientFactory { -	return &ProductionLDAPClientFactory{} +func (f *StandardLDAPClientFactory) Close() (err error) { +	return nil  } -// DialURL creates a client from an LDAP URL when successful. -func (f *ProductionLDAPClientFactory) DialURL(addr string, opts ...ldap.DialOpt) (client LDAPClient, err error) { -	return ldap.DialURL(addr, opts...) +// NewPooledLDAPClientFactory is a decorator for a LDAPClientFactory that performs pooling. +func NewPooledLDAPClientFactory(config *schema.AuthenticationBackendLDAP, certs *x509.CertPool, dialer LDAPClientDialer) (factory LDAPClientFactory) { +	if dialer == nil { +		dialer = &LDAPClientDialerStandard{} +	} + +	tlsc := utils.NewTLSConfig(config.TLS, certs) + +	opts := []ldap.DialOpt{ +		ldap.DialWithDialer(&net.Dialer{Timeout: config.Timeout}), +		ldap.DialWithTLSConfig(tlsc), +	} + +	if config.Pooling.Count <= 0 { +		config.Pooling.Count = 3 +	} + +	if config.Pooling.Retries <= 0 { +		config.Pooling.Retries = 3 +	} + +	if config.Pooling.Timeout <= 0 { +		config.Pooling.Timeout = time.Second +	} + +	sleep := config.Pooling.Timeout / time.Duration(config.Pooling.Retries) + +	return &PooledLDAPClientFactory{ +		config: config, +		tls:    tlsc, +		opts:   opts, +		dialer: dialer, +		sleep:  sleep, +	} +} + +// PooledLDAPClientFactory is a LDAPClientFactory that takes another LDAPClientFactory and pools the +// factory generated connections using a channel for thread safety. +type PooledLDAPClientFactory struct { +	config *schema.AuthenticationBackendLDAP +	tls    *tls.Config +	opts   []ldap.DialOpt +	dialer LDAPClientDialer + +	pool chan *LDAPClientPooled +	mu   sync.Mutex + +	sleep time.Duration + +	closing bool +} + +func (f *PooledLDAPClientFactory) Initialize() (err error) { +	f.mu.Lock() + +	defer f.mu.Unlock() + +	if f.pool != nil { +		return nil +	} + +	f.pool = make(chan *LDAPClientPooled, f.config.Pooling.Count) + +	var ( +		errs   []error +		client *LDAPClientPooled +	) + +	for i := 0; i < f.config.Pooling.Count; i++ { +		if client, err = f.new(); err != nil { +			errs = append(errs, err) + +			continue +		} + +		f.pool <- client +	} + +	if len(errs) == f.config.Pooling.Count { +		return fmt.Errorf("errors occurred initializing the client pool: no connections could be established") +	} + +	return nil +} + +// GetClient opens new client using the pool. +func (f *PooledLDAPClientFactory) GetClient(opts ...LDAPClientFactoryOption) (conn ldap.Client, err error) { +	if len(opts) != 0 { +		return getLDAPClient(f.config.Address.String(), f.config.User, f.config.Password, f.dialer, f.tls, f.config.StartTLS, f.opts, opts...) +	} + +	return f.acquire(context.Background()) +} + +// The new function creates a pool based client. This function is not thread safe. +func (f *PooledLDAPClientFactory) new() (pooled *LDAPClientPooled, err error) { +	var client ldap.Client + +	if client, err = getLDAPClient(f.config.Address.String(), f.config.User, f.config.Password, f.dialer, f.tls, f.config.StartTLS, f.opts); err != nil { +		return nil, fmt.Errorf("error occurred establishing new client for the pool: %w", err) +	} + +	return &LDAPClientPooled{Client: client}, nil +} + +// ReleaseClient returns a client using the pool or closes it. +func (f *PooledLDAPClientFactory) ReleaseClient(client ldap.Client) (err error) { +	f.mu.Lock() + +	defer f.mu.Unlock() + +	if f.closing { +		return client.Close() +	} + +	if pool, ok := client.(*LDAPClientPooled); !ok || cap(f.pool) == len(f.pool) { +		// Prevent extra or non-pool connections from being returned into the pool. +		return client.Close() +	} else { +		f.pool <- pool +	} + +	return nil +} + +func (f *PooledLDAPClientFactory) acquire(ctx context.Context) (client *LDAPClientPooled, err error) { +	f.mu.Lock() + +	defer f.mu.Unlock() + +	if f.closing { +		return nil, fmt.Errorf("error acquiring client: the pool is closed") +	} + +	if cap(f.pool) != f.config.Pooling.Count { +		if err = f.Initialize(); err != nil { +			return nil, err +		} +	} + +	ctx, cancel := context.WithTimeout(ctx, f.config.Pooling.Timeout) +	defer cancel() + +	select { +	case <-ctx.Done(): +		return nil, ctx.Err() +	case client = <-f.pool: +		if client.IsClosing() || client.Client == nil { +			for { +				select { +				case <-ctx.Done(): +					return nil, ctx.Err() +				default: +					if client, err = f.new(); err != nil { +						time.Sleep(f.sleep) + +						continue +					} + +					return client, nil +				} +			} +		} + +		return client, nil +	} +} + +func (f *PooledLDAPClientFactory) Close() (err error) { +	f.mu.Lock() + +	defer f.mu.Unlock() + +	f.closing = true + +	close(f.pool) + +	var errs []error + +	for client := range f.pool { +		if client.IsClosing() { +			continue +		} + +		if err = client.Close(); err != nil { +			errs = append(errs, err) +		} +	} + +	if len(errs) > 0 { +		return fmt.Errorf("errors occurred closing the client pool: %w", errors.Join(errs...)) +	} + +	return nil +} + +// LDAPClientPooled is a decorator for the ldap.Client which handles the pooling functionality. i.e. prevents the client +// from being closed and instead relinquishes the connection back to the pool. +type LDAPClientPooled struct { +	ldap.Client +} + +func getLDAPClient(address, username, password string, dialer LDAPClientDialer, tls *tls.Config, startTLS bool, dialerOpts []ldap.DialOpt, opts ...LDAPClientFactoryOption) (client ldap.Client, err error) { +	config := &LDAPClientFactoryOptions{ +		Address:  address, +		Username: username, +		Password: password, +	} + +	for _, opt := range opts { +		opt(config) +	} + +	if client, err = dialer.DialURL(config.Address, dialerOpts...); err != nil { +		return nil, fmt.Errorf("error occurred dialing address: %w", err) +	} + +	if tls != nil && startTLS { +		if err = client.StartTLS(tls); err != nil { +			_ = client.Close() + +			return nil, fmt.Errorf("error occurred performing starttls: %w", err) +		} +	} + +	if config.Password == "" { +		err = client.UnauthenticatedBind(config.Username) +	} else { +		err = client.Bind(config.Username, config.Password) +	} + +	if err != nil { +		_ = client.Close() + +		return nil, fmt.Errorf("error occurred performing bind: %w", err) +	} + +	return client, nil  } diff --git a/internal/authentication/ldap_client_factory_config.go b/internal/authentication/ldap_client_factory_config.go new file mode 100644 index 000000000..c66222c79 --- /dev/null +++ b/internal/authentication/ldap_client_factory_config.go @@ -0,0 +1,27 @@ +package authentication + +type LDAPClientFactoryOptions struct { +	Address  string +	Username string +	Password string +} + +type LDAPClientFactoryOption func(*LDAPClientFactoryOptions) + +func WithAddress(address string) func(*LDAPClientFactoryOptions) { +	return func(settings *LDAPClientFactoryOptions) { +		settings.Address = address +	} +} + +func WithUsername(username string) func(*LDAPClientFactoryOptions) { +	return func(settings *LDAPClientFactoryOptions) { +		settings.Username = username +	} +} + +func WithPassword(password string) func(*LDAPClientFactoryOptions) { +	return func(settings *LDAPClientFactoryOptions) { +		settings.Password = password +	} +} diff --git a/internal/authentication/ldap_client_factory_mock_test.go b/internal/authentication/ldap_client_factory_mock_test.go index f8ce5bb81..fe0a8c2e4 100644 --- a/internal/authentication/ldap_client_factory_mock_test.go +++ b/internal/authentication/ldap_client_factory_mock_test.go @@ -40,22 +40,63 @@ func (m *MockLDAPClientFactory) EXPECT() *MockLDAPClientFactoryMockRecorder {  	return m.recorder  } -// DialURL mocks base method. -func (m *MockLDAPClientFactory) DialURL(addr string, opts ...ldap.DialOpt) (LDAPClient, error) { +// Close mocks base method. +func (m *MockLDAPClientFactory) Close() error {  	m.ctrl.T.Helper() -	varargs := []any{addr} +	ret := m.ctrl.Call(m, "Close") +	ret0, _ := ret[0].(error) +	return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockLDAPClientFactoryMockRecorder) Close() *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockLDAPClientFactory)(nil).Close)) +} + +// GetClient mocks base method. +func (m *MockLDAPClientFactory) GetClient(opts ...LDAPClientFactoryOption) (ldap.Client, error) { +	m.ctrl.T.Helper() +	varargs := []any{}  	for _, a := range opts {  		varargs = append(varargs, a)  	} -	ret := m.ctrl.Call(m, "DialURL", varargs...) -	ret0, _ := ret[0].(LDAPClient) +	ret := m.ctrl.Call(m, "GetClient", varargs...) +	ret0, _ := ret[0].(ldap.Client)  	ret1, _ := ret[1].(error)  	return ret0, ret1  } -// DialURL indicates an expected call of DialURL. -func (mr *MockLDAPClientFactoryMockRecorder) DialURL(addr any, opts ...any) *gomock.Call { +// GetClient indicates an expected call of GetClient. +func (mr *MockLDAPClientFactoryMockRecorder) GetClient(opts ...any) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetClient", reflect.TypeOf((*MockLDAPClientFactory)(nil).GetClient), opts...) +} + +// Initialize mocks base method. +func (m *MockLDAPClientFactory) Initialize() error { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "Initialize") +	ret0, _ := ret[0].(error) +	return ret0 +} + +// Initialize indicates an expected call of Initialize. +func (mr *MockLDAPClientFactoryMockRecorder) Initialize() *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Initialize", reflect.TypeOf((*MockLDAPClientFactory)(nil).Initialize)) +} + +// ReleaseClient mocks base method. +func (m *MockLDAPClientFactory) ReleaseClient(client ldap.Client) error { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "ReleaseClient", client) +	ret0, _ := ret[0].(error) +	return ret0 +} + +// ReleaseClient indicates an expected call of ReleaseClient. +func (mr *MockLDAPClientFactoryMockRecorder) ReleaseClient(client any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	varargs := append([]any{addr}, opts...) -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DialURL", reflect.TypeOf((*MockLDAPClientFactory)(nil).DialURL), varargs...) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReleaseClient", reflect.TypeOf((*MockLDAPClientFactory)(nil).ReleaseClient), client)  } diff --git a/internal/authentication/ldap_client_mock_test.go b/internal/authentication/ldap_client_mock_test.go index 3a492be2e..055beecbe 100644 --- a/internal/authentication/ldap_client_mock_test.go +++ b/internal/authentication/ldap_client_mock_test.go @@ -1,15 +1,16 @@  // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/authelia/authelia/v4/internal/authentication (interfaces: LDAPClient) +// Source: github.com/go-ldap/ldap/v3 (interfaces: Client)  //  // Generated by this command:  // -//	mockgen -package authentication -destination ldap_client_mock_test.go -mock_names LDAPClient=MockLDAPClient github.com/authelia/authelia/v4/internal/authentication LDAPClient +//	mockgen -package authentication -destination ldap_client_mock_test.go -mock_names Client=MockLDAPClient github.com/go-ldap/ldap/v3 Client  //  // Package authentication is a generated GoMock package.  package authentication  import ( +	context "context"  	tls "crypto/tls"  	reflect "reflect"  	time "time" @@ -18,7 +19,7 @@ import (  	gomock "go.uber.org/mock/gomock"  ) -// MockLDAPClient is a mock of LDAPClient interface. +// MockLDAPClient is a mock of Client interface.  type MockLDAPClient struct {  	ctrl     *gomock.Controller  	recorder *MockLDAPClientMockRecorder @@ -43,17 +44,17 @@ func (m *MockLDAPClient) EXPECT() *MockLDAPClientMockRecorder {  }  // Add mocks base method. -func (m *MockLDAPClient) Add(request *ldap.AddRequest) error { +func (m *MockLDAPClient) Add(arg0 *ldap.AddRequest) error {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "Add", request) +	ret := m.ctrl.Call(m, "Add", arg0)  	ret0, _ := ret[0].(error)  	return ret0  }  // Add indicates an expected call of Add. -func (mr *MockLDAPClientMockRecorder) Add(request any) *gomock.Call { +func (mr *MockLDAPClientMockRecorder) Add(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockLDAPClient)(nil).Add), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockLDAPClient)(nil).Add), arg0)  }  // Bind mocks base method. @@ -100,160 +101,146 @@ func (mr *MockLDAPClientMockRecorder) Compare(dn, attribute, value any) *gomock.  }  // Del mocks base method. -func (m *MockLDAPClient) Del(request *ldap.DelRequest) error { +func (m *MockLDAPClient) Del(arg0 *ldap.DelRequest) error {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "Del", request) +	ret := m.ctrl.Call(m, "Del", arg0)  	ret0, _ := ret[0].(error)  	return ret0  }  // Del indicates an expected call of Del. -func (mr *MockLDAPClientMockRecorder) Del(request any) *gomock.Call { +func (mr *MockLDAPClientMockRecorder) Del(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Del", reflect.TypeOf((*MockLDAPClient)(nil).Del), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Del", reflect.TypeOf((*MockLDAPClient)(nil).Del), arg0)  } -// DigestMD5Bind mocks base method. -func (m *MockLDAPClient) DigestMD5Bind(request *ldap.DigestMD5BindRequest) (*ldap.DigestMD5BindResult, error) { +// DirSync mocks base method. +func (m *MockLDAPClient) DirSync(searchRequest *ldap.SearchRequest, flags, maxAttrCount int64, cookie []byte) (*ldap.SearchResult, error) {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "DigestMD5Bind", request) -	ret0, _ := ret[0].(*ldap.DigestMD5BindResult) +	ret := m.ctrl.Call(m, "DirSync", searchRequest, flags, maxAttrCount, cookie) +	ret0, _ := ret[0].(*ldap.SearchResult)  	ret1, _ := ret[1].(error)  	return ret0, ret1  } -// DigestMD5Bind indicates an expected call of DigestMD5Bind. -func (mr *MockLDAPClientMockRecorder) DigestMD5Bind(request any) *gomock.Call { +// DirSync indicates an expected call of DirSync. +func (mr *MockLDAPClientMockRecorder) DirSync(searchRequest, flags, maxAttrCount, cookie any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DigestMD5Bind", reflect.TypeOf((*MockLDAPClient)(nil).DigestMD5Bind), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DirSync", reflect.TypeOf((*MockLDAPClient)(nil).DirSync), searchRequest, flags, maxAttrCount, cookie)  } -// ExternalBind mocks base method. -func (m *MockLDAPClient) ExternalBind() error { +// DirSyncAsync mocks base method. +func (m *MockLDAPClient) DirSyncAsync(ctx context.Context, searchRequest *ldap.SearchRequest, bufferSize int, flags, maxAttrCount int64, cookie []byte) ldap.Response {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "ExternalBind") -	ret0, _ := ret[0].(error) +	ret := m.ctrl.Call(m, "DirSyncAsync", ctx, searchRequest, bufferSize, flags, maxAttrCount, cookie) +	ret0, _ := ret[0].(ldap.Response)  	return ret0  } -// ExternalBind indicates an expected call of ExternalBind. -func (mr *MockLDAPClientMockRecorder) ExternalBind() *gomock.Call { +// DirSyncAsync indicates an expected call of DirSyncAsync. +func (mr *MockLDAPClientMockRecorder) DirSyncAsync(ctx, searchRequest, bufferSize, flags, maxAttrCount, cookie any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExternalBind", reflect.TypeOf((*MockLDAPClient)(nil).ExternalBind)) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DirSyncAsync", reflect.TypeOf((*MockLDAPClient)(nil).DirSyncAsync), ctx, searchRequest, bufferSize, flags, maxAttrCount, cookie)  } -// IsClosing mocks base method. -func (m *MockLDAPClient) IsClosing() bool { +// Extended mocks base method. +func (m *MockLDAPClient) Extended(arg0 *ldap.ExtendedRequest) (*ldap.ExtendedResponse, error) {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "IsClosing") -	ret0, _ := ret[0].(bool) -	return ret0 +	ret := m.ctrl.Call(m, "Extended", arg0) +	ret0, _ := ret[0].(*ldap.ExtendedResponse) +	ret1, _ := ret[1].(error) +	return ret0, ret1  } -// IsClosing indicates an expected call of IsClosing. -func (mr *MockLDAPClientMockRecorder) IsClosing() *gomock.Call { +// Extended indicates an expected call of Extended. +func (mr *MockLDAPClientMockRecorder) Extended(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsClosing", reflect.TypeOf((*MockLDAPClient)(nil).IsClosing)) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Extended", reflect.TypeOf((*MockLDAPClient)(nil).Extended), arg0)  } -// MD5Bind mocks base method. -func (m *MockLDAPClient) MD5Bind(host, username, password string) error { +// ExternalBind mocks base method. +func (m *MockLDAPClient) ExternalBind() error {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "MD5Bind", host, username, password) +	ret := m.ctrl.Call(m, "ExternalBind")  	ret0, _ := ret[0].(error)  	return ret0  } -// MD5Bind indicates an expected call of MD5Bind. -func (mr *MockLDAPClientMockRecorder) MD5Bind(host, username, password any) *gomock.Call { +// ExternalBind indicates an expected call of ExternalBind. +func (mr *MockLDAPClientMockRecorder) ExternalBind() *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MD5Bind", reflect.TypeOf((*MockLDAPClient)(nil).MD5Bind), host, username, password) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExternalBind", reflect.TypeOf((*MockLDAPClient)(nil).ExternalBind))  } -// Modify mocks base method. -func (m *MockLDAPClient) Modify(request *ldap.ModifyRequest) error { +// GetLastError mocks base method. +func (m *MockLDAPClient) GetLastError() error {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "Modify", request) +	ret := m.ctrl.Call(m, "GetLastError")  	ret0, _ := ret[0].(error)  	return ret0  } -// Modify indicates an expected call of Modify. -func (mr *MockLDAPClientMockRecorder) Modify(request any) *gomock.Call { +// GetLastError indicates an expected call of GetLastError. +func (mr *MockLDAPClientMockRecorder) GetLastError() *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Modify", reflect.TypeOf((*MockLDAPClient)(nil).Modify), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastError", reflect.TypeOf((*MockLDAPClient)(nil).GetLastError))  } -// ModifyDN mocks base method. -func (m_2 *MockLDAPClient) ModifyDN(m *ldap.ModifyDNRequest) error { -	m_2.ctrl.T.Helper() -	ret := m_2.ctrl.Call(m_2, "ModifyDN", m) -	ret0, _ := ret[0].(error) -	return ret0 -} - -// ModifyDN indicates an expected call of ModifyDN. -func (mr *MockLDAPClientMockRecorder) ModifyDN(m any) *gomock.Call { -	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ModifyDN", reflect.TypeOf((*MockLDAPClient)(nil).ModifyDN), m) -} - -// ModifyWithResult mocks base method. -func (m *MockLDAPClient) ModifyWithResult(request *ldap.ModifyRequest) (*ldap.ModifyResult, error) { +// IsClosing mocks base method. +func (m *MockLDAPClient) IsClosing() bool {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "ModifyWithResult", request) -	ret0, _ := ret[0].(*ldap.ModifyResult) -	ret1, _ := ret[1].(error) -	return ret0, ret1 +	ret := m.ctrl.Call(m, "IsClosing") +	ret0, _ := ret[0].(bool) +	return ret0  } -// ModifyWithResult indicates an expected call of ModifyWithResult. -func (mr *MockLDAPClientMockRecorder) ModifyWithResult(request any) *gomock.Call { +// IsClosing indicates an expected call of IsClosing. +func (mr *MockLDAPClientMockRecorder) IsClosing() *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ModifyWithResult", reflect.TypeOf((*MockLDAPClient)(nil).ModifyWithResult), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsClosing", reflect.TypeOf((*MockLDAPClient)(nil).IsClosing))  } -// NTLMBind mocks base method. -func (m *MockLDAPClient) NTLMBind(domain, username, password string) error { +// Modify mocks base method. +func (m *MockLDAPClient) Modify(arg0 *ldap.ModifyRequest) error {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "NTLMBind", domain, username, password) +	ret := m.ctrl.Call(m, "Modify", arg0)  	ret0, _ := ret[0].(error)  	return ret0  } -// NTLMBind indicates an expected call of NTLMBind. -func (mr *MockLDAPClientMockRecorder) NTLMBind(domain, username, password any) *gomock.Call { +// Modify indicates an expected call of Modify. +func (mr *MockLDAPClientMockRecorder) Modify(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NTLMBind", reflect.TypeOf((*MockLDAPClient)(nil).NTLMBind), domain, username, password) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Modify", reflect.TypeOf((*MockLDAPClient)(nil).Modify), arg0)  } -// NTLMBindWithHash mocks base method. -func (m *MockLDAPClient) NTLMBindWithHash(domain, username, hash string) error { +// ModifyDN mocks base method. +func (m *MockLDAPClient) ModifyDN(arg0 *ldap.ModifyDNRequest) error {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "NTLMBindWithHash", domain, username, hash) +	ret := m.ctrl.Call(m, "ModifyDN", arg0)  	ret0, _ := ret[0].(error)  	return ret0  } -// NTLMBindWithHash indicates an expected call of NTLMBindWithHash. -func (mr *MockLDAPClientMockRecorder) NTLMBindWithHash(domain, username, hash any) *gomock.Call { +// ModifyDN indicates an expected call of ModifyDN. +func (mr *MockLDAPClientMockRecorder) ModifyDN(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NTLMBindWithHash", reflect.TypeOf((*MockLDAPClient)(nil).NTLMBindWithHash), domain, username, hash) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ModifyDN", reflect.TypeOf((*MockLDAPClient)(nil).ModifyDN), arg0)  } -// NTLMChallengeBind mocks base method. -func (m *MockLDAPClient) NTLMChallengeBind(request *ldap.NTLMBindRequest) (*ldap.NTLMBindResult, error) { +// ModifyWithResult mocks base method. +func (m *MockLDAPClient) ModifyWithResult(arg0 *ldap.ModifyRequest) (*ldap.ModifyResult, error) {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "NTLMChallengeBind", request) -	ret0, _ := ret[0].(*ldap.NTLMBindResult) +	ret := m.ctrl.Call(m, "ModifyWithResult", arg0) +	ret0, _ := ret[0].(*ldap.ModifyResult)  	ret1, _ := ret[1].(error)  	return ret0, ret1  } -// NTLMChallengeBind indicates an expected call of NTLMChallengeBind. -func (mr *MockLDAPClientMockRecorder) NTLMChallengeBind(request any) *gomock.Call { +// ModifyWithResult indicates an expected call of ModifyWithResult. +func (mr *MockLDAPClientMockRecorder) ModifyWithResult(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NTLMChallengeBind", reflect.TypeOf((*MockLDAPClient)(nil).NTLMChallengeBind), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ModifyWithResult", reflect.TypeOf((*MockLDAPClient)(nil).ModifyWithResult), arg0)  }  // NTLMUnauthenticatedBind mocks base method. @@ -271,89 +258,129 @@ func (mr *MockLDAPClientMockRecorder) NTLMUnauthenticatedBind(domain, username a  }  // PasswordModify mocks base method. -func (m *MockLDAPClient) PasswordModify(request *ldap.PasswordModifyRequest) (*ldap.PasswordModifyResult, error) { +func (m *MockLDAPClient) PasswordModify(arg0 *ldap.PasswordModifyRequest) (*ldap.PasswordModifyResult, error) {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "PasswordModify", request) +	ret := m.ctrl.Call(m, "PasswordModify", arg0)  	ret0, _ := ret[0].(*ldap.PasswordModifyResult)  	ret1, _ := ret[1].(error)  	return ret0, ret1  }  // PasswordModify indicates an expected call of PasswordModify. -func (mr *MockLDAPClientMockRecorder) PasswordModify(request any) *gomock.Call { +func (mr *MockLDAPClientMockRecorder) PasswordModify(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordModify", reflect.TypeOf((*MockLDAPClient)(nil).PasswordModify), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordModify", reflect.TypeOf((*MockLDAPClient)(nil).PasswordModify), arg0)  }  // Search mocks base method. -func (m *MockLDAPClient) Search(request *ldap.SearchRequest) (*ldap.SearchResult, error) { +func (m *MockLDAPClient) Search(arg0 *ldap.SearchRequest) (*ldap.SearchResult, error) {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "Search", request) +	ret := m.ctrl.Call(m, "Search", arg0)  	ret0, _ := ret[0].(*ldap.SearchResult)  	ret1, _ := ret[1].(error)  	return ret0, ret1  }  // Search indicates an expected call of Search. -func (mr *MockLDAPClientMockRecorder) Search(request any) *gomock.Call { +func (mr *MockLDAPClientMockRecorder) Search(arg0 any) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockLDAPClient)(nil).Search), arg0) +} + +// SearchAsync mocks base method. +func (m *MockLDAPClient) SearchAsync(ctx context.Context, searchRequest *ldap.SearchRequest, bufferSize int) ldap.Response { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "SearchAsync", ctx, searchRequest, bufferSize) +	ret0, _ := ret[0].(ldap.Response) +	return ret0 +} + +// SearchAsync indicates an expected call of SearchAsync. +func (mr *MockLDAPClientMockRecorder) SearchAsync(ctx, searchRequest, bufferSize any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Search", reflect.TypeOf((*MockLDAPClient)(nil).Search), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchAsync", reflect.TypeOf((*MockLDAPClient)(nil).SearchAsync), ctx, searchRequest, bufferSize)  }  // SearchWithPaging mocks base method. -func (m *MockLDAPClient) SearchWithPaging(request *ldap.SearchRequest, pagingSize uint32) (*ldap.SearchResult, error) { +func (m *MockLDAPClient) SearchWithPaging(searchRequest *ldap.SearchRequest, pagingSize uint32) (*ldap.SearchResult, error) {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "SearchWithPaging", request, pagingSize) +	ret := m.ctrl.Call(m, "SearchWithPaging", searchRequest, pagingSize)  	ret0, _ := ret[0].(*ldap.SearchResult)  	ret1, _ := ret[1].(error)  	return ret0, ret1  }  // SearchWithPaging indicates an expected call of SearchWithPaging. -func (mr *MockLDAPClientMockRecorder) SearchWithPaging(request, pagingSize any) *gomock.Call { +func (mr *MockLDAPClientMockRecorder) SearchWithPaging(searchRequest, pagingSize any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchWithPaging", reflect.TypeOf((*MockLDAPClient)(nil).SearchWithPaging), request, pagingSize) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchWithPaging", reflect.TypeOf((*MockLDAPClient)(nil).SearchWithPaging), searchRequest, pagingSize)  }  // SetTimeout mocks base method. -func (m *MockLDAPClient) SetTimeout(timeout time.Duration) { +func (m *MockLDAPClient) SetTimeout(arg0 time.Duration) {  	m.ctrl.T.Helper() -	m.ctrl.Call(m, "SetTimeout", timeout) +	m.ctrl.Call(m, "SetTimeout", arg0)  }  // SetTimeout indicates an expected call of SetTimeout. -func (mr *MockLDAPClientMockRecorder) SetTimeout(timeout any) *gomock.Call { +func (mr *MockLDAPClientMockRecorder) SetTimeout(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimeout", reflect.TypeOf((*MockLDAPClient)(nil).SetTimeout), timeout) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTimeout", reflect.TypeOf((*MockLDAPClient)(nil).SetTimeout), arg0)  }  // SimpleBind mocks base method. -func (m *MockLDAPClient) SimpleBind(request *ldap.SimpleBindRequest) (*ldap.SimpleBindResult, error) { +func (m *MockLDAPClient) SimpleBind(arg0 *ldap.SimpleBindRequest) (*ldap.SimpleBindResult, error) {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "SimpleBind", request) +	ret := m.ctrl.Call(m, "SimpleBind", arg0)  	ret0, _ := ret[0].(*ldap.SimpleBindResult)  	ret1, _ := ret[1].(error)  	return ret0, ret1  }  // SimpleBind indicates an expected call of SimpleBind. -func (mr *MockLDAPClientMockRecorder) SimpleBind(request any) *gomock.Call { +func (mr *MockLDAPClientMockRecorder) SimpleBind(arg0 any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SimpleBind", reflect.TypeOf((*MockLDAPClient)(nil).SimpleBind), request) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SimpleBind", reflect.TypeOf((*MockLDAPClient)(nil).SimpleBind), arg0) +} + +// Start mocks base method. +func (m *MockLDAPClient) Start() { +	m.ctrl.T.Helper() +	m.ctrl.Call(m, "Start") +} + +// Start indicates an expected call of Start. +func (mr *MockLDAPClientMockRecorder) Start() *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*MockLDAPClient)(nil).Start))  }  // StartTLS mocks base method. -func (m *MockLDAPClient) StartTLS(config *tls.Config) error { +func (m *MockLDAPClient) StartTLS(arg0 *tls.Config) error {  	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "StartTLS", config) +	ret := m.ctrl.Call(m, "StartTLS", arg0)  	ret0, _ := ret[0].(error)  	return ret0  }  // StartTLS indicates an expected call of StartTLS. -func (mr *MockLDAPClientMockRecorder) StartTLS(config any) *gomock.Call { +func (mr *MockLDAPClientMockRecorder) StartTLS(arg0 any) *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTLS", reflect.TypeOf((*MockLDAPClient)(nil).StartTLS), arg0) +} + +// Syncrepl mocks base method. +func (m *MockLDAPClient) Syncrepl(ctx context.Context, searchRequest *ldap.SearchRequest, bufferSize int, mode ldap.ControlSyncRequestMode, cookie []byte, reloadHint bool) ldap.Response { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "Syncrepl", ctx, searchRequest, bufferSize, mode, cookie, reloadHint) +	ret0, _ := ret[0].(ldap.Response) +	return ret0 +} + +// Syncrepl indicates an expected call of Syncrepl. +func (mr *MockLDAPClientMockRecorder) Syncrepl(ctx, searchRequest, bufferSize, mode, cookie, reloadHint any) *gomock.Call {  	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StartTLS", reflect.TypeOf((*MockLDAPClient)(nil).StartTLS), config) +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Syncrepl", reflect.TypeOf((*MockLDAPClient)(nil).Syncrepl), ctx, searchRequest, bufferSize, mode, cookie, reloadHint)  }  // TLSConnectionState mocks base method. @@ -398,18 +425,3 @@ func (mr *MockLDAPClientMockRecorder) Unbind() *gomock.Call {  	mr.mock.ctrl.T.Helper()  	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unbind", reflect.TypeOf((*MockLDAPClient)(nil).Unbind))  } - -// WhoAmI mocks base method. -func (m *MockLDAPClient) WhoAmI(controls []ldap.Control) (*ldap.WhoAmIResult, error) { -	m.ctrl.T.Helper() -	ret := m.ctrl.Call(m, "WhoAmI", controls) -	ret0, _ := ret[0].(*ldap.WhoAmIResult) -	ret1, _ := ret[1].(error) -	return ret0, ret1 -} - -// WhoAmI indicates an expected call of WhoAmI. -func (mr *MockLDAPClientMockRecorder) WhoAmI(controls any) *gomock.Call { -	mr.mock.ctrl.T.Helper() -	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WhoAmI", reflect.TypeOf((*MockLDAPClient)(nil).WhoAmI), controls) -} diff --git a/internal/authentication/ldap_user_provider.go b/internal/authentication/ldap_user_provider.go index 66687e1a1..905bf07e8 100644 --- a/internal/authentication/ldap_user_provider.go +++ b/internal/authentication/ldap_user_provider.go @@ -1,10 +1,8 @@  package authentication  import ( -	"crypto/tls"  	"crypto/x509"  	"fmt" -	"net"  	"net/url"  	"strconv"  	"strings" @@ -21,11 +19,9 @@ import (  // LDAPUserProvider is a UserProvider that connects to LDAP servers like ActiveDirectory, OpenLDAP, OpenDJ, FreeIPA, etc.  type LDAPUserProvider struct { -	config    schema.AuthenticationBackendLDAP -	tlsConfig *tls.Config -	dialOpts  []ldap.DialOpt -	log       *logrus.Logger -	factory   LDAPClientFactory +	config  *schema.AuthenticationBackendLDAP +	log     *logrus.Logger +	factory LDAPClientFactory  	clock clock.Provider @@ -53,37 +49,27 @@ type LDAPUserProvider struct {  	groupsFilterReplacementsMemberOfRDN bool  } -// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory. -func NewLDAPUserProvider(config schema.AuthenticationBackend, certPool *x509.CertPool) (provider *LDAPUserProvider) { -	provider = NewLDAPUserProviderWithFactory(*config.LDAP, config.PasswordReset.Disable, certPool, NewProductionLDAPClientFactory()) - -	return provider -} - -// NewLDAPUserProviderWithFactory creates a new instance of LDAPUserProvider with the specified LDAPClientFactory. -func NewLDAPUserProviderWithFactory(config schema.AuthenticationBackendLDAP, disableResetPassword bool, certPool *x509.CertPool, factory LDAPClientFactory) (provider *LDAPUserProvider) { -	if config.TLS == nil { -		config.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS +// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the StandardLDAPClientFactory. +func NewLDAPUserProvider(config schema.AuthenticationBackend, certs *x509.CertPool) (provider *LDAPUserProvider) { +	if config.LDAP.TLS == nil { +		config.LDAP.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS  	} -	tlsConfig := utils.NewTLSConfig(config.TLS, certPool) +	var factory LDAPClientFactory -	var dialOpts = []ldap.DialOpt{ -		ldap.DialWithDialer(&net.Dialer{Timeout: config.Timeout}), -	} - -	if tlsConfig != nil { -		dialOpts = append(dialOpts, ldap.DialWithTLSConfig(tlsConfig)) +	if config.LDAP.Pooling.Enable { +		factory = NewPooledLDAPClientFactory(config.LDAP, certs, nil) +	} else { +		factory = NewStandardLDAPClientFactory(config.LDAP, certs, nil)  	} -	if factory == nil { -		factory = NewProductionLDAPClientFactory() -	} +	return NewLDAPUserProviderWithFactory(config.LDAP, config.PasswordReset.Disable, factory) +} +// NewLDAPUserProviderWithFactory creates a new instance of LDAPUserProvider with the specified LDAPClientFactory. +func NewLDAPUserProviderWithFactory(config *schema.AuthenticationBackendLDAP, disableResetPassword bool, factory LDAPClientFactory) (provider *LDAPUserProvider) {  	provider = &LDAPUserProvider{  		config:               config, -		tlsConfig:            tlsConfig, -		dialOpts:             dialOpts,  		log:                  logging.Logger(),  		factory:              factory,  		disableResetPassword: disableResetPassword, @@ -99,25 +85,33 @@ func NewLDAPUserProviderWithFactory(config schema.AuthenticationBackendLDAP, dis  // CheckUserPassword checks if provided password matches for the given user.  func (p *LDAPUserProvider) CheckUserPassword(username string, password string) (valid bool, err error) {  	var ( -		client, clientUser LDAPClient -		profile            *ldapUserProfile +		client, uclient ldap.Client +		profile         *ldapUserProfile  	) -	if client, err = p.connect(); err != nil { +	if client, err = p.factory.GetClient(); err != nil {  		return false, err  	} -	defer client.Close() +	defer func() { +		if err := p.factory.ReleaseClient(client); err != nil { +			p.log.WithError(err).Warn("Error occurred releasing the LDAP client") +		} +	}()  	if profile, err = p.getUserProfile(client, username); err != nil {  		return false, err  	} -	if clientUser, err = p.connectCustom(p.config.Address.String(), profile.DN, password, p.config.StartTLS, p.dialOpts...); err != nil { +	if uclient, err = p.factory.GetClient(WithUsername(profile.DN), WithPassword(password)); err != nil {  		return false, fmt.Errorf("authentication failed. Cause: %w", err)  	} -	defer clientUser.Close() +	defer func() { +		if err := p.factory.ReleaseClient(uclient); err != nil { +			p.log.WithError(err).Warn("Error occurred releasing the LDAP client") +		} +	}()  	return true, nil  } @@ -125,15 +119,19 @@ func (p *LDAPUserProvider) CheckUserPassword(username string, password string) (  // GetDetails retrieve the groups a user belongs to.  func (p *LDAPUserProvider) GetDetails(username string) (details *UserDetails, err error) {  	var ( -		client  LDAPClient +		client  ldap.Client  		profile *ldapUserProfile  	) -	if client, err = p.connect(); err != nil { +	if client, err = p.factory.GetClient(); err != nil {  		return nil, err  	} -	defer client.Close() +	defer func() { +		if err := p.factory.ReleaseClient(client); err != nil { +			p.log.WithError(err).Warn("Error occurred releasing the LDAP client") +		} +	}()  	if profile, err = p.getUserProfile(client, username); err != nil {  		return nil, err @@ -158,11 +156,11 @@ func (p *LDAPUserProvider) GetDetails(username string) (details *UserDetails, er  // GetDetailsExtended retrieves the UserDetailsExtended values.  func (p *LDAPUserProvider) GetDetailsExtended(username string) (details *UserDetailsExtended, err error) {  	var ( -		client  LDAPClient +		client  ldap.Client  		profile *ldapUserProfileExtended  	) -	if client, err = p.connect(); err != nil { +	if client, err = p.factory.GetClient(); err != nil {  		return nil, err  	} @@ -243,15 +241,19 @@ func (p *LDAPUserProvider) GetDetailsExtended(username string) (details *UserDet  // UpdatePassword update the password of the given user.  func (p *LDAPUserProvider) UpdatePassword(username, password string) (err error) {  	var ( -		client  LDAPClient +		client  ldap.Client  		profile *ldapUserProfile  	) -	if client, err = p.connect(); err != nil { +	if client, err = p.factory.GetClient(); err != nil {  		return fmt.Errorf("unable to update password. Cause: %w", err)  	} -	defer client.Close() +	defer func() { +		if err := p.factory.ReleaseClient(client); err != nil { +			p.log.WithError(err).Warn("Error occurred releasing the LDAP client") +		} +	}()  	if profile, err = p.getUserProfile(client, username); err != nil {  		return fmt.Errorf("unable to update password. Cause: %w", err) @@ -297,39 +299,7 @@ func (p *LDAPUserProvider) UpdatePassword(username, password string) (err error)  	return nil  } -func (p *LDAPUserProvider) connect() (client LDAPClient, err error) { -	return p.connectCustom(p.config.Address.String(), p.config.User, p.config.Password, p.config.StartTLS, p.dialOpts...) -} - -func (p *LDAPUserProvider) connectCustom(url, username, password string, startTLS bool, opts ...ldap.DialOpt) (client LDAPClient, err error) { -	if client, err = p.factory.DialURL(url, opts...); err != nil { -		return nil, fmt.Errorf("dial failed with error: %w", err) -	} - -	if startTLS { -		if err = client.StartTLS(p.tlsConfig); err != nil { -			client.Close() - -			return nil, fmt.Errorf("starttls failed with error: %w", err) -		} -	} - -	if password == "" { -		err = client.UnauthenticatedBind(username) -	} else { -		err = client.Bind(username, password) -	} - -	if err != nil { -		client.Close() - -		return nil, fmt.Errorf("bind failed with error: %w", err) -	} - -	return client, nil -} - -func (p *LDAPUserProvider) search(client LDAPClient, request *ldap.SearchRequest) (result *ldap.SearchResult, err error) { +func (p *LDAPUserProvider) search(client ldap.Client, request *ldap.SearchRequest) (result *ldap.SearchResult, err error) {  	if result, err = client.Search(request); err != nil {  		if referral, ok := p.getReferral(err); ok {  			if result == nil { @@ -357,15 +327,19 @@ func (p *LDAPUserProvider) search(client LDAPClient, request *ldap.SearchRequest  func (p *LDAPUserProvider) searchReferral(referral string, request *ldap.SearchRequest, searchResult *ldap.SearchResult) (err error) {  	var ( -		client LDAPClient +		client ldap.Client  		result *ldap.SearchResult  	) -	if client, err = p.connectCustom(referral, p.config.User, p.config.Password, p.config.StartTLS, p.dialOpts...); err != nil { +	if client, err = p.factory.GetClient(WithAddress(referral)); err != nil {  		return fmt.Errorf("error occurred connecting to referred LDAP server '%s': %w", referral, err)  	} -	defer client.Close() +	defer func() { +		if err := p.factory.ReleaseClient(client); err != nil { +			p.log.WithError(err).Warn("Error occurred releasing the LDAP client") +		} +	}()  	if result, err = client.Search(request); err != nil {  		return fmt.Errorf("error occurred performing search on referred LDAP server '%s': %w", referral, err) @@ -390,7 +364,7 @@ func (p *LDAPUserProvider) searchReferrals(request *ldap.SearchRequest, result *  	return nil  } -func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (profile *ldapUserProfile, err error) { +func (p *LDAPUserProvider) getUserProfile(client ldap.Client, username string) (profile *ldapUserProfile, err error) {  	// Search for the given username.  	request := ldap.NewSearchRequest(  		p.usersBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, @@ -463,7 +437,7 @@ func (p *LDAPUserProvider) getUserProfileResultToProfile(username string, entry  	return &userProfile, nil  } -func (p *LDAPUserProvider) getUserProfileExtended(client LDAPClient, username string) (profile *ldapUserProfileExtended, err error) { +func (p *LDAPUserProvider) getUserProfileExtended(client ldap.Client, username string) (profile *ldapUserProfileExtended, err error) {  	// Search for the given username.  	request := ldap.NewSearchRequest(  		p.usersBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, @@ -552,7 +526,7 @@ func (p *LDAPUserProvider) getUserProfileResultToProfileExtended(username string  	return &userProfile, nil  } -func (p *LDAPUserProvider) getUserGroups(client LDAPClient, username string, profile *ldapUserProfile) (groups []string, err error) { +func (p *LDAPUserProvider) getUserGroups(client ldap.Client, 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, @@ -577,7 +551,7 @@ func (p *LDAPUserProvider) getUserGroups(client LDAPClient, username string, pro  	}  } -func (p *LDAPUserProvider) getUserGroupsRequestFilter(client LDAPClient, username string, _ *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) { +func (p *LDAPUserProvider) getUserGroupsRequestFilter(client ldap.Client, username string, _ *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) {  	var result *ldap.SearchResult  	if result, err = p.search(client, request); err != nil { @@ -593,7 +567,7 @@ func (p *LDAPUserProvider) getUserGroupsRequestFilter(client LDAPClient, usernam  	return groups, nil  } -func (p *LDAPUserProvider) getUserGroupsRequestMemberOf(client LDAPClient, username string, profile *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) { +func (p *LDAPUserProvider) getUserGroupsRequestMemberOf(client ldap.Client, username string, profile *ldapUserProfile, request *ldap.SearchRequest) (groups []string, err error) {  	var result *ldap.SearchResult  	if result, err = p.search(client, request); err != nil { @@ -733,7 +707,7 @@ func (p *LDAPUserProvider) resolveGroupsFilter(input string, profile *ldapUserPr  	return filter  } -func (p *LDAPUserProvider) modify(client LDAPClient, modifyRequest *ldap.ModifyRequest) (err error) { +func (p *LDAPUserProvider) modify(client ldap.Client, modifyRequest *ldap.ModifyRequest) (err error) {  	if err = client.Modify(modifyRequest); err != nil {  		var (  			referral string @@ -747,15 +721,19 @@ func (p *LDAPUserProvider) modify(client LDAPClient, modifyRequest *ldap.ModifyR  		p.log.Debugf("Attempting Modify on referred URL %s", referral)  		var ( -			clientRef LDAPClient +			clientRef ldap.Client  			errRef    error  		) -		if clientRef, errRef = p.connectCustom(referral, p.config.User, p.config.Password, p.config.StartTLS, p.dialOpts...); errRef != nil { +		if clientRef, errRef = p.factory.GetClient(WithAddress(referral)); errRef != nil {  			return fmt.Errorf("error occurred connecting to referred LDAP server '%s': %+v. Original Error: %w", referral, errRef, err)  		} -		defer clientRef.Close() +		defer func() { +			if err := p.factory.ReleaseClient(clientRef); err != nil { +				p.log.WithError(err).Warn("Error occurred releasing the LDAP client") +			} +		}()  		if errRef = clientRef.Modify(modifyRequest); errRef != nil {  			return fmt.Errorf("error occurred performing modify on referred LDAP server '%s': %+v. Original Error: %w", referral, errRef, err) @@ -767,7 +745,7 @@ func (p *LDAPUserProvider) modify(client LDAPClient, modifyRequest *ldap.ModifyR  	return nil  } -func (p *LDAPUserProvider) pwdModify(client LDAPClient, pwdModifyRequest *ldap.PasswordModifyRequest) (err error) { +func (p *LDAPUserProvider) pwdModify(client ldap.Client, pwdModifyRequest *ldap.PasswordModifyRequest) (err error) {  	if _, err = client.PasswordModify(pwdModifyRequest); err != nil {  		var (  			referral string @@ -781,15 +759,19 @@ func (p *LDAPUserProvider) pwdModify(client LDAPClient, pwdModifyRequest *ldap.P  		p.log.Debugf("Attempting PwdModify ExOp (1.3.6.1.4.1.4203.1.11.1) on referred URL %s", referral)  		var ( -			clientRef LDAPClient +			clientRef ldap.Client  			errRef    error  		) -		if clientRef, errRef = p.connectCustom(referral, p.config.User, p.config.Password, p.config.StartTLS, p.dialOpts...); errRef != nil { +		if clientRef, errRef = p.factory.GetClient(WithAddress(referral)); errRef != nil {  			return fmt.Errorf("error occurred connecting to referred LDAP server '%s': %+v. Original Error: %w", referral, errRef, err)  		} -		defer clientRef.Close() +		defer func() { +			if err := p.factory.ReleaseClient(clientRef); err != nil { +				p.log.WithError(err).Warn("Error occurred releasing the LDAP client") +			} +		}()  		if _, errRef = clientRef.PasswordModify(pwdModifyRequest); errRef != nil {  			return fmt.Errorf("error occurred performing password modify on referred LDAP server '%s': %+v. Original Error: %w", referral, errRef, err) diff --git a/internal/authentication/ldap_user_provider_startup.go b/internal/authentication/ldap_user_provider_lifecycle.go index 20d7e7653..73dceaa6a 100644 --- a/internal/authentication/ldap_user_provider_startup.go +++ b/internal/authentication/ldap_user_provider_lifecycle.go @@ -10,15 +10,27 @@ import (  	"github.com/authelia/authelia/v4/internal/utils"  ) +func (p *LDAPUserProvider) Shutdown() (err error) { +	return p.factory.Close() +} +  // StartupCheck implements the startup check provider interface.  func (p *LDAPUserProvider) StartupCheck() (err error) { -	var client LDAPClient +	if err = p.factory.Initialize(); err != nil { +		return err +	} + +	var client ldap.Client -	if client, err = p.connect(); err != nil { +	if client, err = p.factory.GetClient(); err != nil {  		return err  	} -	defer client.Close() +	defer func() { +		if err := p.factory.ReleaseClient(client); err != nil { +			p.log.WithError(err).Warn("Error occurred releasing the LDAP client") +		} +	}()  	if p.features, err = p.getServerSupportedFeatures(client); err != nil {  		return err @@ -40,7 +52,7 @@ func (p *LDAPUserProvider) StartupCheck() (err error) {  	return nil  } -func (p *LDAPUserProvider) getServerSupportedFeatures(client LDAPClient) (features LDAPSupportedFeatures, err error) { +func (p *LDAPUserProvider) getServerSupportedFeatures(client ldap.Client) (features LDAPSupportedFeatures, err error) {  	var (  		request *ldap.SearchRequest  		result  *ldap.SearchResult diff --git a/internal/authentication/ldap_user_provider_test.go b/internal/authentication/ldap_user_provider_test.go index 04a37e5f2..3975faa39 100644 --- a/internal/authentication/ldap_user_provider_test.go +++ b/internal/authentication/ldap_user_provider_test.go @@ -25,42 +25,35 @@ func TestNewLDAPUserProvider(t *testing.T) {  	assert.NotNil(t, provider)  } -func TestNewLDAPUserProviderWithFactoryWithoutFactory(t *testing.T) { -	provider := NewLDAPUserProviderWithFactory(schema.AuthenticationBackendLDAP{}, false, nil, nil) - -	assert.NotNil(t, provider) - -	assert.IsType(t, &ProductionLDAPClientFactory{}, provider.factory) -} -  func TestShouldCreateRawConnectionWhenSchemeIsLDAP(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer)  	mockClient := NewMockLDAPClient(ctrl)  	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -		}, +		config,  		false, -		nil, -		mockFactory) +		factory) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURL := mockDialer.EXPECT().DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).Return(mockClient, nil) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	gomock.InOrder(dialURL, connBind) +	gomock.InOrder(dialURL, clientBind) -	_, err := provider.connect() +	_, err := provider.factory.GetClient()  	require.NoError(t, err)  } @@ -69,30 +62,29 @@ func TestShouldCreateTLSConnectionWhenSchemeIsLDAPS(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPSAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPSAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -		}, -		false, -		nil, -		mockFactory) +	provider := NewLDAPUserProviderWithFactory(config, false, factory) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldaps://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURL := mockDialer.EXPECT().DialURL("ldaps://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	gomock.InOrder(dialURL, connBind) +	gomock.InOrder(dialURL, clientBind) -	_, err := provider.connect() +	_, err := provider.factory.GetClient()  	require.NoError(t, err)  } @@ -120,16 +112,16 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:      testLDAPSAddress, +		GroupsFilter: "(|(member={dn})(uid={username})(uid={input}))", +	} -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:      testLDAPSAddress, -			GroupsFilter: "(|(member={dn})(uid={username})(uid={input}))", -		}, -		false, -		nil, -		mockFactory) +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) + +	provider := NewLDAPUserProviderWithFactory(config, false, factory)  	profile := ldapUserProfile{  		DN:          "cn=john (external),dc=example,dc=com", @@ -149,7 +141,15 @@ func TestResolveGroupsFilter(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPSAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer)  	testCases := []struct {  		name     string @@ -207,11 +207,7 @@ func TestResolveGroupsFilter(t *testing.T) {  	for _, tc := range testCases {  		t.Run(tc.name, func(t *testing.T) { -			provider := NewLDAPUserProviderWithFactory( -				tc.have, -				false, -				nil, -				mockFactory) +			provider := NewLDAPUserProviderWithFactory(&tc.have, false, factory)  			assert.Equal(t, tc.expected, provider.resolveGroupsFilter("", tc.profile))  		}) @@ -305,13 +301,12 @@ func TestShouldCheckLDAPEpochFilters(t *testing.T) {  	for _, tc := range testCases {  		t.Run(tc.name, func(t *testing.T) {  			provider := NewLDAPUserProviderWithFactory( -				schema.AuthenticationBackendLDAP{ +				&schema.AuthenticationBackendLDAP{  					UsersFilter: tc.have.users,  					Attributes:  tc.have.attr,  					BaseDN:      "dc=example,dc=com",  				},  				false, -				nil,  				mockFactory)  			assert.Equal(t, tc.expected.dtgeneralized, provider.usersFilterReplacementDateTimeGeneralized) @@ -325,33 +320,102 @@ func TestShouldCheckLDAPServerExtensions(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:     testLDAPAddress, -			User:        "cn=admin,dc=example,dc=com", -			UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", +	provider := NewLDAPUserProviderWithFactory(config, false, factory) + +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	clientBind := mockClient.EXPECT(). +		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). +		Return(nil) + +	searchOIDs := mockClient.EXPECT(). +		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})). +		Return(&ldap.SearchResult{ +			Entries: []*ldap.Entry{ +				{ +					DN: "", +					Attributes: []*ldap.EntryAttribute{ +						{ +							Name:   ldapSupportedExtensionAttribute, +							Values: []string{ldapOIDExtensionPwdModifyExOp, ldapOIDExtensionTLS}, +						}, +						{ +							Name:   ldapSupportedControlAttribute, +							Values: []string{}, +						}, +					}, +				},  			}, -			Password:          "password", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", +		}, nil) + +	clientClose := mockClient.EXPECT().Close() + +	gomock.InOrder(dialURL, clientBind, searchOIDs, clientClose) + +	err := provider.StartupCheck() +	assert.NoError(t, err) + +	assert.True(t, provider.features.Extensions.PwdModifyExOp) +	assert.True(t, provider.features.Extensions.TLS) + +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) +} + +func TestShouldCheckLDAPServerExtensionsPooled(t *testing.T) { +	ctrl := gomock.NewController(t) +	defer ctrl.Finish() + +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf",  		}, -		false, -		nil, -		mockFactory) +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		Pooling: schema.AuthenticationBackendLDAPPooling{ +			Count: 1, +		}, +	} -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	mockDialer := NewMockLDAPClientDialer(ctrl) -	connBind := mockClient.EXPECT(). +	factory := NewPooledLDAPClientFactory(config, nil, mockDialer) + +	mockClient := NewMockLDAPClient(ctrl) + +	provider := NewLDAPUserProviderWithFactory(config, false, factory) + +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -375,9 +439,14 @@ func TestShouldCheckLDAPServerExtensions(t *testing.T) {  			},  		}, nil) -	connClose := mockClient.EXPECT().Close() - -	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) +	gomock.InOrder( +		dialURL, +		clientBind, +		mockClient.EXPECT().IsClosing().Return(false), +		searchOIDs, +		mockClient.EXPECT().IsClosing().Return(false), +		mockClient.EXPECT().Close().Return(fmt.Errorf("close error")), +	)  	err := provider.StartupCheck()  	assert.NoError(t, err) @@ -387,39 +456,105 @@ func TestShouldCheckLDAPServerExtensions(t *testing.T) {  	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints)  	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) + +	assert.EqualError(t, provider.Shutdown(), "errors occurred closing the client pool: close error")  }  func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntry(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:     testLDAPAddress, -			User:        "cn=admin,dc=example,dc=com", -			UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT(). +		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). +		Return(nil) + +	searchOIDs := mockClient.EXPECT(). +		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})). +		Return(&ldap.SearchResult{ +			Entries: []*ldap.Entry{ +				{ +					DN: "", +					Attributes: []*ldap.EntryAttribute{ +						{ +							Name:   ldapSupportedExtensionAttribute, +							Values: []string{ldapOIDExtensionPwdModifyExOp, ldapOIDExtensionTLS}, +						}, +						{ +							Name:   ldapSupportedControlAttribute, +							Values: []string{}, +						}, +					}, +				}, +				{},  			}, -			Password:          "password", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", +		}, nil) + +	clientClose := mockClient.EXPECT().Close() + +	gomock.InOrder(dialURL, clientBind, searchOIDs, clientClose) + +	err := provider.StartupCheck() +	assert.NoError(t, err) + +	assert.False(t, provider.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.TLS) + +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) +} + +func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntryPooled(t *testing.T) { +	ctrl := gomock.NewController(t) +	defer ctrl.Finish() + +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf",  		}, -		false, -		nil, -		mockFactory) +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		Pooling:           schema.AuthenticationBackendLDAPPooling{Count: 1}, +	} -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	mockDialer := NewMockLDAPClientDialer(ctrl) -	connBind := mockClient.EXPECT(). +	mockClient := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewPooledLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -444,9 +579,97 @@ func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntry(t  			},  		}, nil) -	connClose := mockClient.EXPECT().Close() +	gomock.InOrder( +		dialURL, +		clientBind, +		mockClient.EXPECT().IsClosing().Return(false), +		searchOIDs, +		mockClient.EXPECT().IsClosing().Return(false), +		mockClient.EXPECT().Close().Return(nil), +	) + +	err := provider.StartupCheck() +	assert.NoError(t, err) + +	assert.False(t, provider.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.TLS) + +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) + +	assert.NoError(t, provider.Shutdown()) +} + +func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntryPooledClosing(t *testing.T) { +	ctrl := gomock.NewController(t) +	defer ctrl.Finish() + +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		Pooling:           schema.AuthenticationBackendLDAPPooling{Count: 1}, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	mockClient := NewMockLDAPClient(ctrl) +	mockClientSecond := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	dialURLSecond := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClientSecond, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewPooledLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT(). +		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). +		Return(nil) -	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) +	clientBindSecond := mockClientSecond.EXPECT(). +		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). +		Return(nil) + +	searchOIDs := mockClientSecond.EXPECT(). +		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})). +		Return(&ldap.SearchResult{ +			Entries: []*ldap.Entry{ +				{ +					DN: "", +					Attributes: []*ldap.EntryAttribute{ +						{ +							Name:   ldapSupportedExtensionAttribute, +							Values: []string{ldapOIDExtensionPwdModifyExOp, ldapOIDExtensionTLS}, +						}, +						{ +							Name:   ldapSupportedControlAttribute, +							Values: []string{}, +						}, +					}, +				}, +				{}, +			}, +		}, nil) + +	gomock.InOrder( +		dialURL, +		clientBind, +		mockClient.EXPECT().IsClosing().Return(true), +		dialURLSecond, +		clientBindSecond, +		searchOIDs, +		mockClientSecond.EXPECT().IsClosing().Return(false), +		mockClientSecond.EXPECT().Close().Return(nil), +	)  	err := provider.StartupCheck()  	assert.NoError(t, err) @@ -456,39 +679,104 @@ func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntry(t  	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints)  	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) + +	assert.NoError(t, provider.Shutdown())  }  func TestShouldCheckLDAPServerControlTypes(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:     testLDAPAddress, -			User:        "cn=admin,dc=example,dc=com", -			UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT(). +		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). +		Return(nil) + +	searchOIDs := mockClient.EXPECT(). +		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})). +		Return(&ldap.SearchResult{ +			Entries: []*ldap.Entry{ +				{ +					DN: "", +					Attributes: []*ldap.EntryAttribute{ +						{ +							Name:   ldapSupportedExtensionAttribute, +							Values: []string{}, +						}, +						{ +							Name:   ldapSupportedControlAttribute, +							Values: []string{ldapOIDControlMsftServerPolicyHints, ldapOIDControlMsftServerPolicyHintsDeprecated}, +						}, +					}, +				},  			}, -			Password:          "password", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", +		}, nil) + +	clientClose := mockClient.EXPECT().Close() + +	gomock.InOrder(dialURL, clientBind, searchOIDs, clientClose) + +	err := provider.StartupCheck() +	assert.NoError(t, err) + +	assert.False(t, provider.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.TLS) + +	assert.True(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.True(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) +} + +func TestShouldCheckLDAPServerControlTypesPooled(t *testing.T) { +	ctrl := gomock.NewController(t) +	defer ctrl.Finish() + +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf",  		}, -		false, -		nil, -		mockFactory) +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		Pooling:           schema.AuthenticationBackendLDAPPooling{Count: 1}, +	} -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	mockDialer := NewMockLDAPClientDialer(ctrl) -	connBind := mockClient.EXPECT(). +	mockClient := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewPooledLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -512,9 +800,16 @@ func TestShouldCheckLDAPServerControlTypes(t *testing.T) {  			},  		}, nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close() -	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) +	gomock.InOrder( +		dialURL, +		clientBind, +		mockClient.EXPECT().IsClosing().Return(false), +		searchOIDs, +		mockClient.EXPECT().IsClosing().Return(false), +		clientClose, +	)  	err := provider.StartupCheck()  	assert.NoError(t, err) @@ -524,39 +819,38 @@ func TestShouldCheckLDAPServerControlTypes(t *testing.T) {  	assert.True(t, provider.features.ControlTypes.MsftPwdPolHints)  	assert.True(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) + +	assert.NoError(t, provider.Shutdown())  }  func TestShouldNotEnablePasswdModifyExtensionOrControlTypes(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:     testLDAPAddress, -			User:        "cn=admin,dc=example,dc=com", -			UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			Password:          "password", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -580,52 +874,123 @@ func TestShouldNotEnablePasswdModifyExtensionOrControlTypes(t *testing.T) {  			},  		}, nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close() -	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) +	gomock.InOrder(dialURL, clientBind, searchOIDs, clientClose) -	err := provider.StartupCheck() -	assert.NoError(t, err) +	assert.NoError(t, provider.StartupCheck())  	assert.False(t, provider.features.Extensions.PwdModifyExOp)  	assert.False(t, provider.features.Extensions.TLS)  	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints)  	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) + +	assert.NoError(t, provider.Shutdown())  } -func TestShouldReturnCheckServerConnectError(t *testing.T) { +func TestShouldNotEnablePasswdModifyExtensionOrControlTypesPooled(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		Pooling: schema.AuthenticationBackendLDAPPooling{ +			Count: 1, +		}, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:     testLDAPAddress, -			User:        "cn=admin,dc=example,dc=com", -			UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewPooledLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT(). +		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). +		Return(nil) + +	searchOIDs := mockClient.EXPECT(). +		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})). +		Return(&ldap.SearchResult{ +			Entries: []*ldap.Entry{ +				{ +					DN: "", +					Attributes: []*ldap.EntryAttribute{ +						{ +							Name:   ldapSupportedExtensionAttribute, +							Values: []string{}, +						}, +						{ +							Name:   ldapSupportedControlAttribute, +							Values: []string{}, +						}, +					}, +				},  			}, -			Password:          "password", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", +		}, nil) + +	clientClose := mockClient.EXPECT().Close() + +	gomock.InOrder( +		dialURL, +		clientBind, +		mockClient.EXPECT().IsClosing().Return(false), +		searchOIDs, +		mockClient.EXPECT().IsClosing().Return(false), +		clientClose, +	) + +	assert.NoError(t, provider.StartupCheck()) + +	assert.False(t, provider.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.TLS) + +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated) + +	assert.NoError(t, provider.Shutdown()) +} + +func TestShouldReturnCheckServerConnectError(t *testing.T) { +	ctrl := gomock.NewController(t) +	defer ctrl.Finish() + +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf",  		}, -		false, -		nil, -		mockFactory) +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} -	mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, errors.New("could not connect")) +	mockDialer := NewMockLDAPClientDialer(ctrl) -	err := provider.StartupCheck() -	assert.EqualError(t, err, "dial failed with error: could not connect") +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) + +	mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(nil, errors.New("could not connect")) + +	assert.EqualError(t, provider.StartupCheck(), "error occurred dialing address: could not connect")  	assert.False(t, provider.features.Extensions.PwdModifyExOp)  } @@ -634,33 +999,76 @@ func TestShouldReturnCheckServerSearchError(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:     testLDAPAddress, -			User:        "cn=admin,dc=example,dc=com", -			UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			Password:          "password", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT(). +		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). +		Return(nil) + +	searchOIDs := mockClient.EXPECT(). +		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})). +		Return(nil, errors.New("could not perform the search")) + +	clientClose := mockClient.EXPECT().Close() + +	gomock.InOrder(dialURL, clientBind, searchOIDs, clientClose) + +	err := provider.StartupCheck() +	assert.EqualError(t, err, "error occurred during RootDSE search: could not perform the search") + +	assert.False(t, provider.features.Extensions.PwdModifyExOp) +} + +func TestShouldReturnCheckServerSearchErrorPooled(t *testing.T) { +	ctrl := gomock.NewController(t) +	defer ctrl.Finish() + +	config := &schema.AuthenticationBackendLDAP{ +		Address:     testLDAPAddress, +		User:        "cn=admin,dc=example,dc=com", +		UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf",  		}, -		false, -		nil, -		mockFactory) +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		Pooling:           schema.AuthenticationBackendLDAPPooling{Count: 1}, +	} -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	mockDialer := NewMockLDAPClientDialer(ctrl) -	connBind := mockClient.EXPECT(). +	mockClient := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewPooledLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -668,48 +1076,98 @@ func TestShouldReturnCheckServerSearchError(t *testing.T) {  		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})).  		Return(nil, errors.New("could not perform the search")) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close() -	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) +	gomock.InOrder( +		dialURL, +		clientBind, +		mockClient.EXPECT().IsClosing().Return(false), +		searchOIDs, +		mockClient.EXPECT().IsClosing().Return(false), +		clientClose, +	)  	err := provider.StartupCheck()  	assert.EqualError(t, err, "error occurred during RootDSE search: could not perform the search")  	assert.False(t, provider.features.Extensions.PwdModifyExOp) + +	assert.NoError(t, provider.Shutdown())  }  func TestShouldPermitRootDSEFailure(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		PermitFeatureDetectionFailure: true, +		Address:                       testLDAPAddress, +		User:                          "cn=admin,dc=example,dc=com", +		UsersFilter:                   "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			PermitFeatureDetectionFailure: true, -			Address:                       testLDAPAddress, -			User:                          "cn=admin,dc=example,dc=com", -			UsersFilter:                   "(|({username_attribute}={input})({mail_attribute}={input}))", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			Password:          "password", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT(). +		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). +		Return(nil) + +	searchOIDs := mockClient.EXPECT(). +		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})). +		Return(nil, errors.New("could not perform the search")) + +	clientClose := mockClient.EXPECT().Close() + +	gomock.InOrder(dialURL, clientBind, searchOIDs, clientClose) + +	assert.NoError(t, provider.StartupCheck()) +} + +func TestShouldPermitRootDSEFailurePooled(t *testing.T) { +	ctrl := gomock.NewController(t) +	defer ctrl.Finish() + +	config := &schema.AuthenticationBackendLDAP{ +		PermitFeatureDetectionFailure: true, +		Address:                       testLDAPAddress, +		User:                          "cn=admin,dc=example,dc=com", +		UsersFilter:                   "(|({username_attribute}={input})({mail_attribute}={input}))", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf",  		}, -		false, -		nil, -		mockFactory) +		Password:          "password", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		Pooling:           schema.AuthenticationBackendLDAPPooling{Count: 1}, +	} -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	mockDialer := NewMockLDAPClientDialer(ctrl) -	connBind := mockClient.EXPECT(). +	mockClient := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewPooledLDAPClientFactory(config, nil, mockDialer)) + +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -717,12 +1175,19 @@ func TestShouldPermitRootDSEFailure(t *testing.T) {  		Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute, ldapSupportedControlAttribute})).  		Return(nil, errors.New("could not perform the search")) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close() -	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) +	gomock.InOrder( +		dialURL, +		clientBind, +		mockClient.EXPECT().IsClosing().Return(false), +		searchOIDs, +		mockClient.EXPECT().IsClosing().Return(false), +		clientClose, +	) -	err := provider.StartupCheck() -	assert.NoError(t, err) +	assert.NoError(t, provider.StartupCheck()) +	assert.NoError(t, provider.Shutdown())  }  type SearchRequestMatcher struct { @@ -750,7 +1215,7 @@ func TestShouldEscapeUserInput(t *testing.T) {  	mockClient := NewMockLDAPClient(ctrl)  	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ +		&schema.AuthenticationBackendLDAP{  			Address:     testLDAPAddress,  			User:        "cn=admin,dc=example,dc=com",  			UsersFilter: "(|({username_attribute}={input})({mail_attribute}={input}))", @@ -766,7 +1231,6 @@ func TestShouldEscapeUserInput(t *testing.T) {  			PermitReferrals:   true,  		},  		false, -		nil,  		mockFactory)  	mockClient.EXPECT(). @@ -783,35 +1247,32 @@ func TestShouldReturnEmailWhenAttributeSameAsUsername(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "mail", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "(&({username_attribute}={input})(objectClass=inetOrgPerson))", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "mail", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "(&({username_attribute}={input})(objectClass=inetOrgPerson))", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	assert.Equal(t, []string{"mail", "displayName", "memberOf"}, provider.usersAttributes) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	assert.Equal(t, []string{"mail", "displayName", "memberOf"}, provider.usersAttributes) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -835,9 +1296,9 @@ func TestShouldReturnEmailWhenAttributeSameAsUsername(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, bind, search) +	gomock.InOrder(dialURL, clientBind, search) -	client, err := provider.connect() +	client, err := provider.factory.GetClient()  	assert.NoError(t, err)  	profile, err := provider.getUserProfile(client, "john@example.com") @@ -857,35 +1318,32 @@ func TestShouldReturnUsernameAndBlankDisplayNameWhenAttributesTheSame(t *testing  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "uid", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=inetOrgPerson))", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "uid", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=inetOrgPerson))", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	assert.Equal(t, []string{"uid", "mail", "memberOf"}, provider.usersAttributes) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	assert.Equal(t, []string{"uid", "mail", "memberOf"}, provider.usersAttributes) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -909,9 +1367,9 @@ func TestShouldReturnUsernameAndBlankDisplayNameWhenAttributesTheSame(t *testing  			},  		}, nil) -	gomock.InOrder(dialURL, bind, search) +	gomock.InOrder(dialURL, clientBind, search) -	client, err := provider.connect() +	client, err := provider.factory.GetClient()  	assert.NoError(t, err)  	profile, err := provider.getUserProfile(client, "john@example.com") @@ -931,35 +1389,32 @@ func TestShouldReturnBlankEmailAndDisplayNameWhenAttrsLenZero(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=inetOrgPerson))", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=inetOrgPerson))", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	assert.Equal(t, []string{"uid", "mail", "displayName", "memberOf"}, provider.usersAttributes) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	assert.Equal(t, []string{"uid", "mail", "displayName", "memberOf"}, provider.usersAttributes) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -991,9 +1446,9 @@ func TestShouldReturnBlankEmailAndDisplayNameWhenAttrsLenZero(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, bind, search) +	gomock.InOrder(dialURL, clientBind, search) -	client, err := provider.connect() +	client, err := provider.factory.GetClient()  	assert.NoError(t, err)  	profile, err := provider.getUserProfile(client, "john@example.com") @@ -1016,7 +1471,7 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {  	mockClient := NewMockLDAPClient(ctrl)  	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ +		&schema.AuthenticationBackendLDAP{  			Address:     testLDAPAddress,  			User:        "cn=admin,dc=example,dc=com",  			UsersFilter: "(&({username_attribute}={input})(&(objectCategory=person)(objectClass=user)))", @@ -1032,7 +1487,6 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {  			PermitReferrals:   true,  		},  		false, -		nil,  		mockFactory)  	assert.Equal(t, []string{"uid", "mail", "displayName", "memberOf"}, provider.usersAttributes) @@ -1092,39 +1546,36 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -1154,7 +1605,7 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -1169,99 +1620,100 @@ func TestLDAPUserProvider_GetDetailsExtended_ShouldPass(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) -	mockClient := NewMockLDAPClient(ctrl) - -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:       "uid", -				Mail:           "mail", -				DisplayName:    "displayName", -				MemberOf:       "memberOf", -				StreetAddress:  "street", -				FamilyName:     "sn", -				MiddleName:     "middle", -				GivenName:      "givenName", -				Nickname:       "nickname", -				Gender:         "gender", -				Birthdate:      "birthDate", -				Website:        "website", -				Profile:        "profile", -				Picture:        "picture", -				ZoneInfo:       "zoneinfo", -				Locale:         "locale", -				PhoneNumber:    "phone", -				PhoneExtension: "ext", -				Locality:       "locality", -				Region:         "region", -				PostalCode:     "postCode", -				Country:        "c", -				Extra: map[string]schema.AuthenticationBackendLDAPAttributesAttribute{ -					"exampleStr": { -						AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -							MultiValued: false, -							ValueType:   ValueTypeString, -						}, +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:       "uid", +			Mail:           "mail", +			DisplayName:    "displayName", +			MemberOf:       "memberOf", +			StreetAddress:  "street", +			FamilyName:     "sn", +			MiddleName:     "middle", +			GivenName:      "givenName", +			Nickname:       "nickname", +			Gender:         "gender", +			Birthdate:      "birthDate", +			Website:        "website", +			Profile:        "profile", +			Picture:        "picture", +			ZoneInfo:       "zoneinfo", +			Locale:         "locale", +			PhoneNumber:    "phone", +			PhoneExtension: "ext", +			Locality:       "locality", +			Region:         "region", +			PostalCode:     "postCode", +			Country:        "c", +			Extra: map[string]schema.AuthenticationBackendLDAPAttributesAttribute{ +				"exampleStr": { +					AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +						MultiValued: false, +						ValueType:   ValueTypeString,  					}, -					"exampleStrMV": { -						AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -							MultiValued: true, -							ValueType:   ValueTypeString, -						}, +				}, +				"exampleStrMV": { +					AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +						MultiValued: true, +						ValueType:   ValueTypeString,  					}, -					"exampleInt": { -						Name: "exampleIntChangedAttributeName", -						AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -							MultiValued: false, -							ValueType:   ValueTypeInteger, -						}, +				}, +				"exampleInt": { +					Name: "exampleIntChangedAttributeName", +					AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +						MultiValued: false, +						ValueType:   ValueTypeInteger,  					}, -					"exampleIntMV": { -						AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -							MultiValued: true, -							ValueType:   ValueTypeInteger, -						}, +				}, +				"exampleIntMV": { +					AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +						MultiValued: true, +						ValueType:   ValueTypeInteger,  					}, -					"exampleBool": { -						AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -							MultiValued: false, -							ValueType:   ValueTypeBoolean, -						}, +				}, +				"exampleBool": { +					AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +						MultiValued: false, +						ValueType:   ValueTypeBoolean,  					}, -					"exampleBoolMV": { -						AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -							MultiValued: true, -							ValueType:   ValueTypeBoolean, -						}, +				}, +				"exampleBoolMV": { +					AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +						MultiValued: true, +						ValueType:   ValueTypeBoolean,  					}, -					"exampleEmptyStringInt": { -						AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -							MultiValued: false, -							ValueType:   ValueTypeInteger, -						}, +				}, +				"exampleEmptyStringInt": { +					AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +						MultiValued: false, +						ValueType:   ValueTypeInteger,  					}, -					"exampleEmptyStringBoolean": { -						AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -							MultiValued: false, -							ValueType:   ValueTypeBoolean, -						}, +				}, +				"exampleEmptyStringBoolean": { +					AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +						MultiValued: false, +						ValueType:   ValueTypeBoolean,  					},  				},  			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true,  		}, -		false, -		nil, -		mockFactory) +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) -	dialURL := mockFactory.EXPECT(). +	provider := NewLDAPUserProviderWithFactory(config, false, factory) + +	mockClient := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(mockClient, nil) @@ -1487,39 +1939,40 @@ func TestLDAPUserProvider_GetDetailsExtended_ShouldParseError(t *testing.T) {  			ctrl := gomock.NewController(t)  			defer ctrl.Finish() -			mockFactory := NewMockLDAPClientFactory(ctrl) -			mockClient := NewMockLDAPClient(ctrl) +			mockDialer := NewMockLDAPClientDialer(ctrl) -			provider := NewLDAPUserProviderWithFactory( -				schema.AuthenticationBackendLDAP{ -					Address:  testLDAPAddress, -					User:     "cn=admin,dc=example,dc=com", -					Password: "password", -					Attributes: schema.AuthenticationBackendLDAPAttributes{ -						Username:      "uid", -						Mail:          "mail", -						DisplayName:   "displayName", -						MemberOf:      "memberOf", -						StreetAddress: "street", -						Extra: map[string]schema.AuthenticationBackendLDAPAttributesAttribute{ -							"example": { -								AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ -									MultiValued: tc.multiValued, -									ValueType:   tc.valueType, -								}, +			config := &schema.AuthenticationBackendLDAP{ +				Address:  testLDAPAddress, +				User:     "cn=admin,dc=example,dc=com", +				Password: "password", +				Attributes: schema.AuthenticationBackendLDAPAttributes{ +					Username:      "uid", +					Mail:          "mail", +					DisplayName:   "displayName", +					MemberOf:      "memberOf", +					StreetAddress: "street", +					Extra: map[string]schema.AuthenticationBackendLDAPAttributesAttribute{ +						"example": { +							AuthenticationBackendExtraAttribute: schema.AuthenticationBackendExtraAttribute{ +								MultiValued: tc.multiValued, +								ValueType:   tc.valueType,  							},  						},  					}, -					UsersFilter:       "uid={input}", -					AdditionalUsersDN: "ou=users", -					BaseDN:            "dc=example,dc=com", -					PermitReferrals:   true,  				}, -				false, -				nil, -				mockFactory) +				UsersFilter:       "uid={input}", +				AdditionalUsersDN: "ou=users", +				BaseDN:            "dc=example,dc=com", +				PermitReferrals:   true, +			} + +			factory := NewStandardLDAPClientFactory(config, nil, mockDialer) + +			provider := NewLDAPUserProviderWithFactory(config, false, factory) -			dialURL := mockFactory.EXPECT(). +			mockClient := NewMockLDAPClient(ctrl) + +			dialURL := mockDialer.EXPECT().  				DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  				Return(mockClient, nil) @@ -1578,31 +2031,32 @@ func TestLDAPUserProvider_GetDetailsExtended_ShouldErrorBadPictureURL(t *testing  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) -	mockClient := NewMockLDAPClient(ctrl) - -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				Picture:     "photoURL", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			Picture:     "photoURL",  		}, -		false, -		nil, -		mockFactory) +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) + +	provider := NewLDAPUserProviderWithFactory(config, false, factory) -	dialURL := mockFactory.EXPECT(). +	mockClient := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(mockClient, nil) @@ -1657,31 +2111,32 @@ func TestLDAPUserProvider_GetDetailsExtended_ShouldErrorBadProfileURL(t *testing  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) -	mockClient := NewMockLDAPClient(ctrl) - -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				Profile:     "profile", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			Profile:     "profile",  		}, -		false, -		nil, -		mockFactory) +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) -	dialURL := mockFactory.EXPECT(). +	provider := NewLDAPUserProviderWithFactory(config, false, factory) + +	mockClient := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(mockClient, nil) @@ -1736,31 +2191,32 @@ func TestLDAPUserProvider_GetDetailsExtended_ShouldErrorBadWebsiteURL(t *testing  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) -	mockClient := NewMockLDAPClient(ctrl) - -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				Website:     "www", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			Website:     "www",  		}, -		false, -		nil, -		mockFactory) +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) + +	provider := NewLDAPUserProviderWithFactory(config, false, factory) + +	mockClient := NewMockLDAPClient(ctrl) -	dialURL := mockFactory.EXPECT(). +	dialURL := mockDialer.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(mockClient, nil) @@ -1815,31 +2271,32 @@ func TestLDAPUserProvider_GetDetailsExtended_ShouldErrorBadLocale(t *testing.T)  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) -	mockClient := NewMockLDAPClient(ctrl) - -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				Locale:      "locale", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			Locale:      "locale",  		}, -		false, -		nil, -		mockFactory) +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) + +	provider := NewLDAPUserProviderWithFactory(config, false, factory) + +	mockClient := NewMockLDAPClient(ctrl) -	dialURL := mockFactory.EXPECT(). +	dialURL := mockDialer.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(mockClient, nil) @@ -1894,44 +2351,41 @@ func TestLDAPUserProvider_GetDetails_ShouldReturnOnUserError(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()).  		Return(nil, fmt.Errorf("failed to search")) -	gomock.InOrder(dialURL, connBind, searchProfile, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, clientClose)  	details, err := provider.GetDetails("john")  	assert.Nil(t, details) @@ -1942,30 +2396,31 @@ func TestLDAPUserProvider_GetDetailsExtendedShouldReturnOnBindError(t *testing.T  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) -	mockClient := NewMockLDAPClient(ctrl) - -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf",  		}, -		false, -		nil, -		mockFactory) +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) -	dialURL := mockFactory.EXPECT(). +	provider := NewLDAPUserProviderWithFactory(config, false, factory) + +	mockClient := NewMockLDAPClient(ctrl) + +	dialURL := mockDialer.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(mockClient, nil) @@ -1979,36 +2434,36 @@ func TestLDAPUserProvider_GetDetailsExtendedShouldReturnOnBindError(t *testing.T  	details, err := provider.GetDetailsExtended("john")  	assert.Nil(t, details) -	assert.EqualError(t, err, "bind failed with error: bad bind") +	assert.EqualError(t, err, "error occurred performing bind: bad bind")  }  func TestLDAPUserProvider_GetDetailsExtendedShouldReturnOnDialError(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) - -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf",  		}, -		false, -		nil, -		mockFactory) +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer) -	dialURL := mockFactory.EXPECT(). +	provider := NewLDAPUserProviderWithFactory(config, false, factory) + +	dialURL := mockDialer.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(nil, fmt.Errorf("failed to dial")) @@ -2016,37 +2471,37 @@ func TestLDAPUserProvider_GetDetailsExtendedShouldReturnOnDialError(t *testing.T  	details, err := provider.GetDetailsExtended("john")  	assert.Nil(t, details) -	assert.EqualError(t, err, "dial failed with error: failed to dial") +	assert.EqualError(t, err, "error occurred dialing address: failed to dial")  }  func TestLDAPUserProvider_GetDetailsExtendedShouldReturnOnUserError(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) + +	factory := NewStandardLDAPClientFactory(config, nil, mockDialer)  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	provider := NewLDAPUserProviderWithFactory(config, false, factory) -	dialURL := mockFactory.EXPECT(). +	dialURL := mockDialer.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(mockClient, nil) @@ -2071,38 +2526,35 @@ func TestLDAPUserProvider_GetDetails_ShouldReturnOnGroupsError(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -2132,7 +2584,7 @@ func TestLDAPUserProvider_GetDetails_ShouldReturnOnGroupsError(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john") @@ -2144,37 +2596,34 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "displayName", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "displayName", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -2196,7 +2645,7 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -2210,37 +2659,34 @@ func TestShouldUnauthenticatedBind(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "displayName", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "displayName", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		UnauthenticatedBind(gomock.Eq("cn=admin,dc=example,dc=com")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -2262,7 +2708,7 @@ func TestShouldUnauthenticatedBind(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -2276,38 +2722,35 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -2337,7 +2780,7 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -2352,40 +2795,37 @@ func TestShouldReturnUsernameFromLDAPSearchModeMemberOfRDN(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		GroupSearchMode:   "memberof", +		UsersFilter:       "uid={input}", +		GroupsFilter:      "(|{memberof:rdn})", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "DC=example,DC=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			GroupSearchMode:   "memberof", -			UsersFilter:       "uid={input}", -			GroupsFilter:      "(|{memberof:rdn})", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "DC=example,DC=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	requestGroups := ldap.NewSearchRequest(  		provider.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, @@ -2427,7 +2867,7 @@ func TestShouldReturnUsernameFromLDAPSearchModeMemberOfRDN(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -2442,41 +2882,38 @@ func TestShouldReturnUsernameFromLDAPSearchModeMemberOfDN(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "CN=Administrator,CN=Users,DC=example,DC=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			DistinguishedName: "distinguishedName", +			Username:          "sAMAccountName", +			Mail:              "mail", +			DisplayName:       "displayName", +			MemberOf:          "memberOf", +			GroupName:         "cn", +		}, +		GroupSearchMode:   "memberof", +		UsersFilter:       "sAMAccountName={input}", +		GroupsFilter:      "(|{memberof:dn})", +		AdditionalUsersDN: "CN=users", +		BaseDN:            "DC=example,DC=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "CN=Administrator,CN=Users,DC=example,DC=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				DistinguishedName: "distinguishedName", -				Username:          "sAMAccountName", -				Mail:              "mail", -				DisplayName:       "displayName", -				MemberOf:          "memberOf", -				GroupName:         "cn", -			}, -			GroupSearchMode:   "memberof", -			UsersFilter:       "sAMAccountName={input}", -			GroupsFilter:      "(|{memberof:dn})", -			AdditionalUsersDN: "CN=users", -			BaseDN:            "DC=example,DC=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("CN=Administrator,CN=Users,DC=example,DC=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	requestGroups := ldap.NewSearchRequest(  		provider.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, @@ -2515,7 +2952,7 @@ func TestShouldReturnUsernameFromLDAPSearchModeMemberOfDN(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -2530,41 +2967,38 @@ func TestShouldReturnErrSearchMemberOf(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "CN=Administrator,CN=Users,DC=example,DC=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			DistinguishedName: "distinguishedName", +			Username:          "sAMAccountName", +			Mail:              "mail", +			DisplayName:       "displayName", +			MemberOf:          "memberOf", +			GroupName:         "cn", +		}, +		GroupSearchMode:   "memberof", +		UsersFilter:       "sAMAccountName={input}", +		GroupsFilter:      "(|{memberof:dn})", +		AdditionalUsersDN: "CN=users", +		BaseDN:            "DC=example,DC=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "CN=Administrator,CN=Users,DC=example,DC=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				DistinguishedName: "distinguishedName", -				Username:          "sAMAccountName", -				Mail:              "mail", -				DisplayName:       "displayName", -				MemberOf:          "memberOf", -				GroupName:         "cn", -			}, -			GroupSearchMode:   "memberof", -			UsersFilter:       "sAMAccountName={input}", -			GroupsFilter:      "(|{memberof:dn})", -			AdditionalUsersDN: "CN=users", -			BaseDN:            "DC=example,DC=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("CN=Administrator,CN=Users,DC=example,DC=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	requestGroups := ldap.NewSearchRequest(  		provider.groupsBaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, @@ -2603,7 +3037,7 @@ func TestShouldReturnErrSearchMemberOf(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	assert.Nil(t, details) @@ -2614,41 +3048,38 @@ func TestShouldReturnErrUnknownSearchMode(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "CN=Administrator,CN=Users,DC=example,DC=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			DistinguishedName: "distinguishedName", +			Username:          "sAMAccountName", +			Mail:              "mail", +			DisplayName:       "displayName", +			MemberOf:          "memberOf", +			GroupName:         "cn", +		}, +		GroupSearchMode:   "bad", +		UsersFilter:       "sAMAccountName={input}", +		GroupsFilter:      "(|{memberof:dn})", +		AdditionalUsersDN: "CN=users", +		BaseDN:            "DC=example,DC=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "CN=Administrator,CN=Users,DC=example,DC=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				DistinguishedName: "distinguishedName", -				Username:          "sAMAccountName", -				Mail:              "mail", -				DisplayName:       "displayName", -				MemberOf:          "memberOf", -				GroupName:         "cn", -			}, -			GroupSearchMode:   "bad", -			UsersFilter:       "sAMAccountName={input}", -			GroupsFilter:      "(|{memberof:dn})", -			AdditionalUsersDN: "CN=users", -			BaseDN:            "DC=example,DC=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("CN=Administrator,CN=Users,DC=example,DC=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -2678,7 +3109,7 @@ func TestShouldReturnErrUnknownSearchMode(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, clientClose)  	details, err := provider.GetDetails("john")  	assert.Nil(t, details) @@ -2690,41 +3121,38 @@ func TestShouldSkipEmptyAttributesSearchModeMemberOf(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "CN=Administrator,CN=Users,DC=example,DC=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			DistinguishedName: "distinguishedName", +			Username:          "sAMAccountName", +			Mail:              "mail", +			DisplayName:       "displayName", +			MemberOf:          "memberOf", +			GroupName:         "cn", +		}, +		GroupSearchMode:   "memberof", +		UsersFilter:       "sAMAccountName={input}", +		GroupsFilter:      "(|{memberof:dn})", +		AdditionalUsersDN: "CN=users", +		BaseDN:            "DC=example,DC=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "CN=Administrator,CN=Users,DC=example,DC=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				DistinguishedName: "distinguishedName", -				Username:          "sAMAccountName", -				Mail:              "mail", -				DisplayName:       "displayName", -				MemberOf:          "memberOf", -				GroupName:         "cn", -			}, -			GroupSearchMode:   "memberof", -			UsersFilter:       "sAMAccountName={input}", -			GroupsFilter:      "(|{memberof:dn})", -			AdditionalUsersDN: "CN=users", -			BaseDN:            "DC=example,DC=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("CN=Administrator,CN=Users,DC=example,DC=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -2792,7 +3220,7 @@ func TestShouldSkipEmptyAttributesSearchModeMemberOf(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john") @@ -2804,41 +3232,38 @@ func TestShouldSkipEmptyAttributesSearchModeFilter(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "CN=Administrator,CN=Users,DC=example,DC=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			DistinguishedName: "distinguishedName", +			Username:          "sAMAccountName", +			Mail:              "mail", +			DisplayName:       "displayName", +			MemberOf:          "memberOf", +			GroupName:         "cn", +		}, +		GroupSearchMode:   "filter", +		UsersFilter:       "sAMAccountName={input}", +		GroupsFilter:      "(|{memberof:dn})", +		AdditionalUsersDN: "CN=users", +		BaseDN:            "DC=example,DC=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "CN=Administrator,CN=Users,DC=example,DC=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				DistinguishedName: "distinguishedName", -				Username:          "sAMAccountName", -				Mail:              "mail", -				DisplayName:       "displayName", -				MemberOf:          "memberOf", -				GroupName:         "cn", -			}, -			GroupSearchMode:   "filter", -			UsersFilter:       "sAMAccountName={input}", -			GroupsFilter:      "(|{memberof:dn})", -			AdditionalUsersDN: "CN=users", -			BaseDN:            "DC=example,DC=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("CN=Administrator,CN=Users,DC=example,DC=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -2906,7 +3331,7 @@ func TestShouldSkipEmptyAttributesSearchModeFilter(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john") @@ -2918,39 +3343,36 @@ func TestShouldSkipEmptyGroupsResultMemberOf(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -2984,7 +3406,7 @@ func TestShouldSkipEmptyGroupsResultMemberOf(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -2998,41 +3420,38 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndResult(t *testing.T)  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl)  	mockClientReferralAlt := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -3045,15 +3464,13 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndResult(t *testing.T)  			Referrals: []string{"ldap://192.168.0.1"},  		}, &ldap.Error{ResultCode: ldap.LDAPResultReferral, Err: errors.New("referral"), Packet: &testBERPacketReferral}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferral, nil) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferral, nil) -	connBindReferral := mockClientReferral.EXPECT(). +	clientBindReferral := mockClientReferral.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferral := mockClientReferral.EXPECT().Close() +	clientCloseReferral := mockClientReferral.EXPECT().Close()  	searchProfileReferral := mockClientReferral.EXPECT().  		Search(gomock.Any()). @@ -3079,15 +3496,13 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndResult(t *testing.T)  			},  		}, nil) -	dialURLReferralAlt := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferralAlt, nil) +	dialURLReferralAlt := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferralAlt, nil) -	connBindReferralAlt := mockClientReferralAlt.EXPECT(). +	clientBindReferralAlt := mockClientReferralAlt.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferralAlt := mockClientReferralAlt.EXPECT().Close() +	clientCloseReferralAlt := mockClientReferralAlt.EXPECT().Close()  	searchProfileReferralAlt := mockClientReferralAlt.EXPECT().  		Search(gomock.Any()). @@ -3113,7 +3528,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndResult(t *testing.T)  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, dialURLReferral, connBindReferral, searchProfileReferral, connCloseReferral, dialURLReferralAlt, connBindReferralAlt, searchProfileReferralAlt, connCloseReferralAlt, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, dialURLReferral, clientBindReferral, searchProfileReferral, clientCloseReferral, dialURLReferralAlt, clientBindReferralAlt, searchProfileReferralAlt, clientCloseReferralAlt, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -3128,40 +3543,37 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndNoResult(t *testing.  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -3171,15 +3583,13 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndNoResult(t *testing.  		Search(gomock.Any()).  		Return(nil, &ldap.Error{ResultCode: ldap.LDAPResultReferral, Err: errors.New("referral"), Packet: &testBERPacketReferral}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferral, nil) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferral, nil) -	connBindReferral := mockClientReferral.EXPECT(). +	clientBindReferral := mockClientReferral.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferral := mockClientReferral.EXPECT().Close() +	clientCloseReferral := mockClientReferral.EXPECT().Close()  	searchProfileReferral := mockClientReferral.EXPECT().  		Search(gomock.Any()). @@ -3205,7 +3615,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndNoResult(t *testing.  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, dialURLReferral, connBindReferral, searchProfileReferral, connCloseReferral, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, dialURLReferral, clientBindReferral, searchProfileReferral, clientCloseReferral, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -3220,114 +3630,104 @@ func TestShouldReturnDialErrDuringReferralSearchUsernameFromLDAPWithReferralsInE  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()).  		Return(nil, &ldap.Error{ResultCode: ldap.LDAPResultReferral, Err: errors.New("referral"), Packet: &testBERPacketReferral}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(nil, fmt.Errorf("failed to connect")) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(nil, fmt.Errorf("failed to connect")) -	gomock.InOrder(dialURL, connBind, searchProfile, dialURLReferral, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, dialURLReferral, clientClose)  	details, err := provider.GetDetails("john")  	assert.Nil(t, details) -	assert.EqualError(t, err, "cannot find user DN of user 'john'. Cause: error occurred connecting to referred LDAP server 'ldap://192.168.0.1': dial failed with error: failed to connect") +	assert.EqualError(t, err, "cannot find user DN of user 'john'. Cause: error occurred connecting to referred LDAP server 'ldap://192.168.0.1': error occurred dialing address: failed to connect")  }  func TestShouldReturnSearchErrDuringReferralSearchUsernameFromLDAPWithReferralsInErrorAndNoResult(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()).  		Return(nil, &ldap.Error{ResultCode: ldap.LDAPResultReferral, Err: errors.New("referral"), Packet: &testBERPacketReferral}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferral, nil) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferral, nil) -	connBindReferral := mockClientReferral.EXPECT(). +	clientBindReferral := mockClientReferral.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferral := mockClientReferral.EXPECT().Close() +	clientCloseReferral := mockClientReferral.EXPECT().Close()  	searchProfileReferral := mockClientReferral.EXPECT().  		Search(gomock.Any()).  		Return(nil, fmt.Errorf("not found")) -	gomock.InOrder(dialURL, connBind, searchProfile, dialURLReferral, connBindReferral, searchProfileReferral, connCloseReferral, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, dialURLReferral, clientBindReferral, searchProfileReferral, clientCloseReferral, clientClose)  	details, err := provider.GetDetails("john") @@ -3339,45 +3739,42 @@ func TestShouldNotReturnUsernameFromLDAPWithReferralsInErrorAndReferralsNotPermi  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   false, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   false, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()).  		Return(nil, &ldap.Error{ResultCode: ldap.LDAPResultReferral, Err: errors.New("referral"), Packet: &testBERPacketReferral}) -	gomock.InOrder(dialURL, connBind, searchProfile, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, clientClose)  	details, err := provider.GetDetails("john")  	assert.EqualError(t, err, "cannot find user DN of user 'john'. Cause: LDAP Result Code 10 \"Referral\": referral") @@ -3388,40 +3785,37 @@ func TestShouldReturnUsernameFromLDAPWithReferralsErr(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -3431,15 +3825,13 @@ func TestShouldReturnUsernameFromLDAPWithReferralsErr(t *testing.T) {  		Search(gomock.Any()).  		Return(&ldap.SearchResult{}, &ldap.Error{ResultCode: ldap.LDAPResultReferral, Err: errors.New("referral"), Packet: &testBERPacketReferral}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferral, nil) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferral, nil) -	connBindReferral := mockClientReferral.EXPECT(). +	clientBindReferral := mockClientReferral.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferral := mockClientReferral.EXPECT().Close() +	clientCloseReferral := mockClientReferral.EXPECT().Close()  	searchProfileReferral := mockClientReferral.EXPECT().  		Search(gomock.Any()). @@ -3465,7 +3857,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsErr(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connBind, searchProfile, dialURLReferral, connBindReferral, searchProfileReferral, connCloseReferral, searchGroups, connClose) +	gomock.InOrder(dialURL, clientBind, searchProfile, dialURLReferral, clientBindReferral, searchProfileReferral, clientCloseReferral, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -3480,34 +3872,33 @@ func TestShouldNotUpdateUserPasswordConnect(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   false, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   false, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(nil, errors.New("tcp timeout")) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBindOIDs := mockClient.EXPECT(). +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -3531,53 +3922,46 @@ func TestShouldNotUpdateUserPasswordConnect(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(nil, errors.New("tcp timeout")) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL) +	require.NoError(t, provider.StartupCheck()) -	err := provider.StartupCheck() -	require.NoError(t, err) - -	err = provider.UpdatePassword("john", "password") -	assert.EqualError(t, err, "unable to update password. Cause: dial failed with error: tcp timeout") +	assert.EqualError(t, provider.UpdatePassword("john", "password"), "unable to update password. Cause: error occurred dialing address: tcp timeout")  }  func TestShouldNotUpdateUserPasswordGetDetails(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   false, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   false, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBindOIDs := mockClient.EXPECT(). +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -3601,56 +3985,51 @@ func TestShouldNotUpdateUserPasswordGetDetails(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()).  		Return(nil, &ldap.Error{ResultCode: ldap.LDAPResultProtocolError, Err: errors.New("permission error")}) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, clientClose) -	err := provider.StartupCheck() -	require.NoError(t, err) +	require.NoError(t, provider.StartupCheck()) -	err = provider.UpdatePassword("john", "password") -	assert.EqualError(t, err, "unable to update password. Cause: cannot find user DN of user 'john'. Cause: LDAP Result Code 2 \"Protocol Error\": permission error") +	assert.EqualError(t, provider.UpdatePassword("john", "password"), "unable to update password. Cause: cannot find user DN of user 'john'. Cause: LDAP Result Code 2 \"Protocol Error\": permission error")  }  func TestShouldUpdateUserPassword(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	modifyRequest := ldap.NewModifyRequest(  		"uid=test,dc=example,dc=com", @@ -3659,11 +4038,9 @@ func TestShouldUpdateUserPassword(t *testing.T) {  	modifyRequest.Replace(ldapAttributeUserPassword, []string{"password"}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -3687,17 +4064,13 @@ func TestShouldUpdateUserPassword(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -3727,7 +4100,7 @@ func TestShouldUpdateUserPassword(t *testing.T) {  		Modify(modifyRequest).  		Return(nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, modify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -3740,28 +4113,29 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "activedirectory", +		Address:        testLDAPAddress, +		User:           "cn=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "activedirectory", -			Address:        testLDAPAddress, -			User:           "cn=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	modifyRequest := ldap.NewModifyRequest(  		"uid=test,dc=example,dc=com", @@ -3771,11 +4145,9 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {  	pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))  	modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -3799,17 +4171,13 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() - -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	clientCloseOIDs := mockClient.EXPECT().Close() -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -3839,7 +4207,7 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {  		Modify(modifyRequest).  		Return(nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, modify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -3852,30 +4220,31 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "activedirectory", +		Address:        testLDAPAddress, +		User:           "cn=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "activedirectory", -			Address:        testLDAPAddress, -			User:           "cn=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	modifyRequest := ldap.NewModifyRequest(  		"uid=test,dc=example,dc=com", @@ -3885,11 +4254,9 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {  	pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))  	modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -3913,17 +4280,13 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -3957,21 +4320,19 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {  			Packet:     &testBERPacketReferral,  		}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferral, nil) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferral, nil) -	connBindReferral := mockClientReferral.EXPECT(). +	clientBindReferral := mockClientReferral.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferral := mockClientReferral.EXPECT().Close() +	clientCloseReferral := mockClientReferral.EXPECT().Close()  	modifyReferral := mockClientReferral.EXPECT().  		Modify(modifyRequest).  		Return(nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, dialURLReferral, connBindReferral, modifyReferral, connCloseReferral, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, modify, dialURLReferral, clientBindReferral, modifyReferral, clientCloseReferral, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -3984,29 +4345,30 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "activedirectory", +		Address:        testLDAPAddress, +		User:           "cn=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "activedirectory", -			Address:        testLDAPAddress, -			User:           "cn=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	modifyRequest := ldap.NewModifyRequest(  		"uid=test,dc=example,dc=com", @@ -4016,11 +4378,9 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test  	pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))  	modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -4044,17 +4404,13 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() - -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	clientCloseOIDs := mockClient.EXPECT().Close() -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -4088,47 +4444,46 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test  			Packet:     &testBERPacketReferral,  		}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(nil, errors.New("tcp timeout")) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(nil, errors.New("tcp timeout")) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, dialURLReferral, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, modify, dialURLReferral, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err)  	err = provider.UpdatePassword("john", "password") -	assert.EqualError(t, err, "unable to update password. Cause: error occurred connecting to referred LDAP server 'ldap://192.168.0.1': dial failed with error: tcp timeout. Original Error: LDAP Result Code 10 \"Referral\": error occurred") +	assert.EqualError(t, err, "unable to update password. Cause: error occurred connecting to referred LDAP server 'ldap://192.168.0.1': error occurred dialing address: tcp timeout. Original Error: LDAP Result Code 10 \"Referral\": error occurred")  }  func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "activedirectory", +		Address:        testLDAPAddress, +		User:           "cn=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "activedirectory", -			Address:        testLDAPAddress, -			User:           "cn=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	modifyRequest := ldap.NewModifyRequest(  		"uid=test,dc=example,dc=com", @@ -4138,11 +4493,9 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi  	pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))  	modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -4166,17 +4519,13 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() - -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	clientCloseOIDs := mockClient.EXPECT().Close() -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -4210,15 +4559,13 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi  			Packet:     &testBERPacketReferral,  		}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferral, nil) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferral, nil) -	connBindReferral := mockClientReferral.EXPECT(). +	clientBindReferral := mockClientReferral.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferral := mockClientReferral.EXPECT().Close() +	clientCloseReferral := mockClientReferral.EXPECT().Close()  	modifyReferral := mockClientReferral.EXPECT().  		Modify(modifyRequest). @@ -4228,7 +4575,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi  			Packet:     &testBERPacketReferral,  		}) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, dialURLReferral, connBindReferral, modifyReferral, connCloseReferral, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, modify, dialURLReferral, clientBindReferral, modifyReferral, clientCloseReferral, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -4241,29 +4588,30 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "activedirectory", +		Address:        testLDAPAddress, +		User:           "cn=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   false, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "activedirectory", -			Address:        testLDAPAddress, -			User:           "cn=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   false, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	modifyRequest := ldap.NewModifyRequest(  		"uid=test,dc=example,dc=com", @@ -4273,11 +4621,9 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {  	pwdEncoded, _ := encodingUTF16LittleEndian.NewEncoder().String(fmt.Sprintf("\"%s\"", "password"))  	modifyRequest.Replace(ldapAttributeUnicodePwd, []string{pwdEncoded}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -4301,17 +4647,13 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() - -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	clientCloseOIDs := mockClient.EXPECT().Close() -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -4345,7 +4687,7 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {  			Packet:     &testBERPacketReferral,  		}) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, modify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -4358,27 +4700,28 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	pwdModifyRequest := ldap.NewPasswordModifyRequest(  		"uid=test,dc=example,dc=com", @@ -4386,11 +4729,9 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {  		"password",  	) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -4414,17 +4755,13 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -4454,7 +4791,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {  		PasswordModify(pwdModifyRequest).  		Return(nil, nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -4467,29 +4804,30 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferrals(t *testing.T  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	pwdModifyRequest := ldap.NewPasswordModifyRequest(  		"uid=test,dc=example,dc=com", @@ -4497,11 +4835,9 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferrals(t *testing.T  		"password",  	) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -4525,17 +4861,13 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferrals(t *testing.T  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -4571,21 +4903,19 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferrals(t *testing.T  			Packet:     &testBERPacketReferral,  		}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferral, nil) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferral, nil) -	connBindReferral := mockClientReferral.EXPECT(). +	clientBindReferral := mockClientReferral.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferral := mockClientReferral.EXPECT().Close() +	clientCloseReferral := mockClientReferral.EXPECT().Close()  	passwdModifyReferral := mockClientReferral.EXPECT().  		PasswordModify(pwdModifyRequest).  		Return(&ldap.PasswordModifyResult{}, nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, dialURLReferral, connBindReferral, passwdModifyReferral, connCloseReferral, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, dialURLReferral, clientBindReferral, passwdModifyReferral, clientCloseReferral, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -4598,28 +4928,29 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithoutReferrals(t *testin  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   false, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   false, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	pwdModifyRequest := ldap.NewPasswordModifyRequest(  		"uid=test,dc=example,dc=com", @@ -4627,11 +4958,9 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithoutReferrals(t *testin  		"password",  	) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -4655,17 +4984,13 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithoutReferrals(t *testin  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -4701,7 +5026,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithoutReferrals(t *testin  			Packet:     &testBERPacketReferral,  		}) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -4714,28 +5039,29 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralConne  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	pwdModifyRequest := ldap.NewPasswordModifyRequest(  		"uid=test,dc=example,dc=com", @@ -4743,11 +5069,9 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralConne  		"password",  	) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -4771,17 +5095,13 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralConne  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -4817,46 +5137,45 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralConne  			Packet:     &testBERPacketReferral,  		}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(nil, errors.New("tcp timeout")) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(nil, errors.New("tcp timeout")) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, dialURLReferral, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, dialURLReferral, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err)  	err = provider.UpdatePassword("john", "password") -	assert.EqualError(t, err, "unable to update password. Cause: error occurred connecting to referred LDAP server 'ldap://192.168.0.1': dial failed with error: tcp timeout. Original Error: LDAP Result Code 10 \"Referral\": error occurred") +	assert.EqualError(t, err, "unable to update password. Cause: error occurred connecting to referred LDAP server 'ldap://192.168.0.1': error occurred dialing address: tcp timeout. Original Error: LDAP Result Code 10 \"Referral\": error occurred")  }  func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralPasswordModifyErr(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		PermitReferrals:   true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			PermitReferrals:   true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	pwdModifyRequest := ldap.NewPasswordModifyRequest(  		"uid=test,dc=example,dc=com", @@ -4864,11 +5183,9 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralPassw  		"password",  	) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -4892,17 +5209,13 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralPassw  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() - -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	clientCloseOIDs := mockClient.EXPECT().Close() -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -4938,15 +5251,13 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralPassw  			Packet:     &testBERPacketReferral,  		}) -	dialURLReferral := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://192.168.0.1"), gomock.Any()). -		Return(mockClientReferral, nil) +	dialURLReferral := mockDialer.EXPECT().DialURL("ldap://192.168.0.1", gomock.Any()).Return(mockClientReferral, nil) -	connBindReferral := mockClientReferral.EXPECT(). +	clientBindReferral := mockClientReferral.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connCloseReferral := mockClientReferral.EXPECT().Close() +	clientCloseReferral := mockClientReferral.EXPECT().Close()  	passwdModifyReferral := mockClientReferral.EXPECT().  		PasswordModify(pwdModifyRequest). @@ -4956,7 +5267,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralPassw  			Packet:     &testBERPacketReferral,  		}) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, dialURLReferral, connBindReferral, passwdModifyReferral, connCloseReferral, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, dialURLReferral, clientBindReferral, passwdModifyReferral, clientCloseReferral, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -4969,29 +5280,30 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHints(t *testing  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "activedirectory", +		Address:        testLDAPAddress, +		User:           "cn=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			DistinguishedName: "distinguishedName", +			Username:          "sAMAccountName", +			Mail:              "mail", +			DisplayName:       "displayName", +			MemberOf:          "memberOf", +		}, +		UsersFilter:       "cn={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "activedirectory", -			Address:        testLDAPAddress, -			User:           "cn=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				DistinguishedName: "distinguishedName", -				Username:          "sAMAccountName", -				Mail:              "mail", -				DisplayName:       "displayName", -				MemberOf:          "memberOf", -			}, -			UsersFilter:       "cn={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)  	pwdEncoded, _ := utf16.NewEncoder().String("\"password\"") @@ -5003,11 +5315,9 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHints(t *testing  	modifyRequest.Replace("unicodePwd", []string{pwdEncoded}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5031,17 +5341,13 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHints(t *testing  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -5071,7 +5377,7 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHints(t *testing  		Modify(modifyRequest).  		Return(nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -5084,29 +5390,30 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHintsDeprecated(  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "activedirectory", +		Address:        testLDAPAddress, +		User:           "cn=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			DistinguishedName: "distinguishedName", +			Username:          "sAMAccountName", +			Mail:              "mail", +			DisplayName:       "displayName", +			MemberOf:          "memberOf", +		}, +		UsersFilter:       "cn={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "activedirectory", -			Address:        testLDAPAddress, -			User:           "cn=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				DistinguishedName: "distinguishedName", -				Username:          "sAMAccountName", -				Mail:              "mail", -				DisplayName:       "displayName", -				MemberOf:          "memberOf", -			}, -			UsersFilter:       "cn={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)  	pwdEncoded, _ := utf16.NewEncoder().String("\"password\"") @@ -5118,11 +5425,9 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHintsDeprecated(  	modifyRequest.Replace("unicodePwd", []string{pwdEncoded}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5146,17 +5451,13 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHintsDeprecated(  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() - -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	clientCloseOIDs := mockClient.EXPECT().Close() -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -5186,7 +5487,7 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHintsDeprecated(  		Modify(modifyRequest).  		Return(nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -5199,29 +5500,30 @@ func TestShouldUpdateUserPasswordActiveDirectory(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "activedirectory", +		Address:        testLDAPAddress, +		User:           "cn=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			DistinguishedName: "distinguishedName", +			Username:          "sAMAccountName", +			Mail:              "mail", +			DisplayName:       "displayName", +			MemberOf:          "memberOf", +		}, +		UsersFilter:       "cn={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "activedirectory", -			Address:        testLDAPAddress, -			User:           "cn=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				DistinguishedName: "distinguishedName", -				Username:          "sAMAccountName", -				Mail:              "mail", -				DisplayName:       "displayName", -				MemberOf:          "memberOf", -			}, -			UsersFilter:       "cn={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM)  	pwdEncoded, _ := utf16.NewEncoder().String("\"password\"") @@ -5233,11 +5535,9 @@ func TestShouldUpdateUserPasswordActiveDirectory(t *testing.T) {  	modifyRequest.Replace("unicodePwd", []string{pwdEncoded}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5261,17 +5561,13 @@ func TestShouldUpdateUserPasswordActiveDirectory(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() - -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	clientCloseOIDs := mockClient.EXPECT().Close() -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -5301,7 +5597,7 @@ func TestShouldUpdateUserPasswordActiveDirectory(t *testing.T) {  		Modify(modifyRequest).  		Return(nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -5314,28 +5610,29 @@ func TestShouldUpdateUserPasswordBasic(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Implementation: "custom", +		Address:        testLDAPAddress, +		User:           "uid=admin,dc=example,dc=com", +		Password:       "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Implementation: "custom", -			Address:        testLDAPAddress, -			User:           "uid=admin,dc=example,dc=com", -			Password:       "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) + +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	modifyRequest := ldap.NewModifyRequest(  		"uid=test,dc=example,dc=com", @@ -5344,11 +5641,9 @@ func TestShouldUpdateUserPasswordBasic(t *testing.T) {  	modifyRequest.Replace("userPassword", []string{"password"}) -	dialURLOIDs := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURLOIDs := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBindOIDs := mockClient.EXPECT(). +	clientBindOIDs := mockClient.EXPECT().  		Bind(gomock.Eq("uid=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5372,17 +5667,13 @@ func TestShouldUpdateUserPasswordBasic(t *testing.T) {  			},  		}, nil) -	connCloseOIDs := mockClient.EXPECT().Close() +	clientCloseOIDs := mockClient.EXPECT().Close() -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) - -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("uid=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchProfile := mockClient.EXPECT().  		Search(gomock.Any()). @@ -5412,7 +5703,7 @@ func TestShouldUpdateUserPasswordBasic(t *testing.T) {  		Modify(modifyRequest).  		Return(nil) -	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) +	gomock.InOrder(dialURLOIDs, clientBindOIDs, searchOIDs, clientCloseOIDs, dialURL, clientBind, searchProfile, passwdModify, clientClose)  	err := provider.StartupCheck()  	require.NoError(t, err) @@ -5425,33 +5716,30 @@ func TestShouldReturnErrorWhenMultipleUsernameAttributes(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5479,9 +5767,9 @@ func TestShouldReturnErrorWhenMultipleUsernameAttributes(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, bind, search) +	gomock.InOrder(dialURL, clientBind, search) -	client, err := provider.connect() +	client, err := provider.factory.GetClient()  	assert.NoError(t, err)  	profile, err := provider.getUserProfile(client, "john") @@ -5494,33 +5782,30 @@ func TestShouldReturnErrorWhenZeroUsernameAttributes(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5548,9 +5833,9 @@ func TestShouldReturnErrorWhenZeroUsernameAttributes(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, bind, search) +	gomock.InOrder(dialURL, clientBind, search) -	client, err := provider.connect() +	client, err := provider.factory.GetClient()  	assert.NoError(t, err)  	profile, err := provider.getUserProfile(client, "john") @@ -5563,33 +5848,30 @@ func TestShouldReturnErrorWhenUsernameAttributeNotReturned(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5613,9 +5895,9 @@ func TestShouldReturnErrorWhenUsernameAttributeNotReturned(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, bind, search) +	gomock.InOrder(dialURL, clientBind, search) -	client, err := provider.connect() +	client, err := provider.factory.GetClient()  	assert.NoError(t, err)  	profile, err := provider.getUserProfile(client, "john") @@ -5628,33 +5910,30 @@ func TestShouldReturnErrorWhenMultipleUsersFound(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "(|(uid={input})(uid=*))", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "(|(uid={input})(uid=*))", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5699,9 +5978,9 @@ func TestShouldReturnErrorWhenMultipleUsersFound(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, bind, search) +	gomock.InOrder(dialURL, clientBind, search) -	client, err := provider.connect() +	client, err := provider.factory.GetClient()  	assert.NoError(t, err)  	profile, err := provider.getUserProfile(client, "john") @@ -5714,33 +5993,30 @@ func TestShouldReturnErrorWhenNoDN(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "(|(uid={input})(uid=*))", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "(|(uid={input})(uid=*))", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) @@ -5768,9 +6044,9 @@ func TestShouldReturnErrorWhenNoDN(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, bind, search) +	gomock.InOrder(dialURL, clientBind, search) -	client, err := provider.connect() +	client, err := provider.factory.GetClient()  	assert.NoError(t, err)  	profile, err := provider.getUserProfile(client, "john") @@ -5783,32 +6059,29 @@ func TestShouldCheckValidUserPassword(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	gomock.InOrder( -		mockFactory.EXPECT(). -			DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -			Return(mockClient, nil), +		mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil),  		mockClient.EXPECT().  			Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  			Return(nil), @@ -5835,9 +6108,7 @@ func TestShouldCheckValidUserPassword(t *testing.T) {  					},  				},  			}, nil), -		mockFactory.EXPECT(). -			DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -			Return(mockClient, nil), +		mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil),  		mockClient.EXPECT().  			Bind(gomock.Eq("uid=test,dc=example,dc=com"), gomock.Eq("password")).  			Return(nil), @@ -5854,74 +6125,68 @@ func TestShouldNotCheckValidUserPasswordWithConnectError(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	bind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(&ldap.Error{ResultCode: ldap.LDAPResultInvalidCredentials, Err: errors.New("invalid username or password")}) -	gomock.InOrder(dialURL, bind, mockClient.EXPECT().Close()) +	gomock.InOrder(dialURL, clientBind, mockClient.EXPECT().Close())  	valid, err := provider.CheckUserPassword("john", "password")  	assert.False(t, valid) -	assert.EqualError(t, err, "bind failed with error: LDAP Result Code 49 \"Invalid Credentials\": invalid username or password") +	assert.EqualError(t, err, "error occurred performing bind: LDAP Result Code 49 \"Invalid Credentials\": invalid username or password")  }  func TestShouldNotCheckValidUserPasswordWithGetProfileError(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	gomock.InOrder( -		mockFactory.EXPECT(). -			DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -			Return(mockClient, nil), +		mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil),  		mockClient.EXPECT().  			Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  			Return(nil), @@ -5941,32 +6206,29 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -		}, -		false, -		nil, -		mockFactory) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	gomock.InOrder( -		mockFactory.EXPECT(). -			DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -			Return(mockClient, nil), +		mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil),  		mockClient.EXPECT().  			Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  			Return(nil), @@ -5993,9 +6255,7 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) {  					},  				},  			}, nil), -		mockFactory.EXPECT(). -			DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -			Return(mockClient, nil), +		mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil),  		mockClient.EXPECT().  			Bind(gomock.Eq("uid=test,dc=example,dc=com"), gomock.Eq("password")).  			Return(errors.New("invalid username or password")), @@ -6005,49 +6265,47 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) {  	valid, err := provider.CheckUserPassword("john", "password")  	assert.False(t, valid) -	require.EqualError(t, err, "authentication failed. Cause: bind failed with error: invalid username or password") +	require.EqualError(t, err, "authentication failed. Cause: error occurred performing bind: invalid username or password")  }  func TestShouldCallStartTLSWhenEnabled(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		TLS:               schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS, +		StartTLS:          true, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			StartTLS:          true, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer)) -	connBind := mockClient.EXPECT(). +	connStartTLS := mockClient.EXPECT(). +		StartTLS(gomock.Any()) + +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil) -	connStartTLS := mockClient.EXPECT(). -		StartTLS(provider.tlsConfig) - -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -6077,7 +6335,7 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {  			},  		}, nil) -	gomock.InOrder(dialURL, connStartTLS, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, connStartTLS, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -6095,7 +6353,7 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ +		&schema.AuthenticationBackendLDAP{  			Address:  testLDAPAddress,  			User:     "cn=admin,dc=example,dc=com",  			Password: "password", @@ -6113,7 +6371,6 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {  			StartTLS:           true,  		},  		false, -		nil,  		mockFactory)  	provider.clock = clock.NewFixed(time.Unix(1670250519, 0)) @@ -6139,49 +6396,46 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +			GroupName:   "cn", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		StartTLS:          true, +		TLS: &schema.TLS{ +			SkipVerify: true, +		}, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -				GroupName:   "cn", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			StartTLS:          true, -			TLS: &schema.TLS{ -				SkipVerify: true, -			}, -		}, -		false, -		nil, -		mockFactory) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	assert.False(t, provider.groupsFilterReplacementInput)  	assert.False(t, provider.groupsFilterReplacementUsername)  	assert.False(t, provider.groupsFilterReplacementDN) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	dialURL := mockDialer.EXPECT().DialURL("ldap://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	connBind := mockClient.EXPECT(). +	clientBind := mockClient.EXPECT().  		Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")).  		Return(nil)  	connStartTLS := mockClient.EXPECT(). -		StartTLS(provider.tlsConfig) +		StartTLS(gomock.Not(gomock.Nil())) -	connClose := mockClient.EXPECT().Close() +	clientClose := mockClient.EXPECT().Close()  	searchGroups := mockClient.EXPECT().  		Search(gomock.Any()). @@ -6215,7 +6469,7 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T  			},  		}, nil) -	gomock.InOrder(dialURL, connStartTLS, connBind, searchProfile, searchGroups, connClose) +	gomock.InOrder(dialURL, connStartTLS, clientBind, searchProfile, searchGroups, clientClose)  	details, err := provider.GetDetails("john")  	require.NoError(t, err) @@ -6230,42 +6484,39 @@ func TestShouldReturnLDAPSAlreadySecuredWhenStartTLSAttempted(t *testing.T) {  	ctrl := gomock.NewController(t)  	defer ctrl.Finish() -	mockFactory := NewMockLDAPClientFactory(ctrl) +	config := &schema.AuthenticationBackendLDAP{ +		Address:  testLDAPSAddress, +		User:     "cn=admin,dc=example,dc=com", +		Password: "password", +		Attributes: schema.AuthenticationBackendLDAPAttributes{ +			Username:    "uid", +			Mail:        "mail", +			DisplayName: "displayName", +			MemberOf:    "memberOf", +		}, +		UsersFilter:       "uid={input}", +		AdditionalUsersDN: "ou=users", +		BaseDN:            "dc=example,dc=com", +		StartTLS:          true, +		TLS: &schema.TLS{ +			SkipVerify: true, +		}, +	} + +	mockDialer := NewMockLDAPClientDialer(ctrl) +  	mockClient := NewMockLDAPClient(ctrl) -	provider := NewLDAPUserProviderWithFactory( -		schema.AuthenticationBackendLDAP{ -			Address:  testLDAPSAddress, -			User:     "cn=admin,dc=example,dc=com", -			Password: "password", -			Attributes: schema.AuthenticationBackendLDAPAttributes{ -				Username:    "uid", -				Mail:        "mail", -				DisplayName: "displayName", -				MemberOf:    "memberOf", -			}, -			UsersFilter:       "uid={input}", -			AdditionalUsersDN: "ou=users", -			BaseDN:            "dc=example,dc=com", -			StartTLS:          true, -			TLS: &schema.TLS{ -				SkipVerify: true, -			}, -		}, -		false, -		nil, -		mockFactory) +	dialURL := mockDialer.EXPECT().DialURL("ldaps://127.0.0.1:389", gomock.Any()).Return(mockClient, nil) -	dialURL := mockFactory.EXPECT(). -		DialURL(gomock.Eq("ldaps://127.0.0.1:389"), gomock.Any()). -		Return(mockClient, nil) +	provider := NewLDAPUserProviderWithFactory(config, false, NewStandardLDAPClientFactory(config, nil, mockDialer))  	connStartTLS := mockClient.EXPECT(). -		StartTLS(provider.tlsConfig). +		StartTLS(gomock.Not(gomock.Nil())).  		Return(errors.New("LDAP Result Code 200 \"Network Error\": ldap: already encrypted"))  	gomock.InOrder(dialURL, connStartTLS, mockClient.EXPECT().Close())  	_, err := provider.GetDetails("john") -	assert.EqualError(t, err, "starttls failed with error: LDAP Result Code 200 \"Network Error\": ldap: already encrypted") +	assert.EqualError(t, err, "error occurred performing starttls: LDAP Result Code 200 \"Network Error\": ldap: already encrypted")  } diff --git a/internal/authentication/types.go b/internal/authentication/types.go index d1853d5f3..145a83818 100644 --- a/internal/authentication/types.go +++ b/internal/authentication/types.go @@ -1,59 +1,13 @@  package authentication  import ( -	"crypto/tls"  	"fmt"  	"net/mail"  	"net/url" -	"time" -	"github.com/go-ldap/ldap/v3"  	"golang.org/x/text/language"  ) -// LDAPClientFactory an interface of factory of LDAP clients. -type LDAPClientFactory interface { -	DialURL(addr string, opts ...ldap.DialOpt) (client LDAPClient, err error) -} - -// LDAPClient is a cut down version of the ldap.Client interface with just the methods we use. -// -// Methods added to this interface that have a direct correlation with one from ldap.Client should have the same signature. -type LDAPClient interface { -	Close() (err error) -	IsClosing() bool -	SetTimeout(timeout time.Duration) - -	TLSConnectionState() (state tls.ConnectionState, ok bool) -	StartTLS(config *tls.Config) (err error) - -	Unbind() (err error) -	Bind(username, password string) (err error) -	SimpleBind(request *ldap.SimpleBindRequest) (result *ldap.SimpleBindResult, err error) -	MD5Bind(host string, username string, password string) (err error) -	DigestMD5Bind(request *ldap.DigestMD5BindRequest) (result *ldap.DigestMD5BindResult, err error) -	UnauthenticatedBind(username string) (err error) -	ExternalBind() (err error) -	NTLMBind(domain string, username string, password string) (err error) -	NTLMUnauthenticatedBind(domain string, username string) (err error) -	NTLMBindWithHash(domain string, username string, hash string) (err error) -	NTLMChallengeBind(request *ldap.NTLMBindRequest) (result *ldap.NTLMBindResult, err error) - -	Modify(request *ldap.ModifyRequest) (err error) -	ModifyWithResult(request *ldap.ModifyRequest) (result *ldap.ModifyResult, err error) -	ModifyDN(m *ldap.ModifyDNRequest) (err error) -	PasswordModify(request *ldap.PasswordModifyRequest) (result *ldap.PasswordModifyResult, err error) - -	Add(request *ldap.AddRequest) (err error) -	Del(request *ldap.DelRequest) (err error) - -	Search(request *ldap.SearchRequest) (result *ldap.SearchResult, err error) -	SearchWithPaging(request *ldap.SearchRequest, pagingSize uint32) (result *ldap.SearchResult, err error) -	Compare(dn string, attribute string, value string) (same bool, err error) - -	WhoAmI(controls []ldap.Control) (result *ldap.WhoAmIResult, err error) -} -  // UserDetails represent the details retrieved for a given user.  type UserDetails struct {  	Username    string diff --git a/internal/authentication/user_provider.go b/internal/authentication/user_provider.go index f6c63ca57..516345d15 100644 --- a/internal/authentication/user_provider.go +++ b/internal/authentication/user_provider.go @@ -13,4 +13,5 @@ type UserProvider interface {  	GetDetails(username string) (details *UserDetails, err error)  	GetDetailsExtended(username string) (details *UserDetailsExtended, err error)  	UpdatePassword(username, newPassword string) (err error) +	Shutdown() (err error)  } diff --git a/internal/commands/services.go b/internal/commands/services.go index 332a29357..18598b488 100644 --- a/internal/commands/services.go +++ b/internal/commands/services.go @@ -447,6 +447,10 @@ func servicesRun(ctx ServiceCtx) {  	var err error +	if err = ctx.GetProviders().UserProvider.Shutdown(); err != nil { +		ctx.GetLogger().WithError(err).Error("Error occurred closing authentication connections") +	} +  	if err = ctx.GetProviders().StorageProvider.Close(); err != nil {  		ctx.GetLogger().WithError(err).Error("Error occurred closing database connections")  	} diff --git a/internal/configuration/config.template.yml b/internal/configuration/config.template.yml index a3181081d..2e01b7a15 100644 --- a/internal/configuration/config.template.yml +++ b/internal/configuration/config.template.yml @@ -459,6 +459,7 @@ identity_validation:      ## Use StartTLS with the LDAP connection.      # start_tls: false +    ## TLS configuration.      # tls:        ## The server subject name to check the servers certificate against during the validation process.        ## This option is not required if the certificate has a SAN which matches the address options hostname. @@ -495,6 +496,20 @@ identity_validation:          # ...          # -----END RSA PRIVATE KEY----- +    ## Connection Pooling configuration. +    # pooling: +      ## Enable Pooling. +      # enable: false + +      ## Pool count. +      # count: 5 + +      ## Retries to obtain a connection during the timeout. +      # retries: 2 + +      ## Timeout before the attempt to obtain a connection fails. +      # timeout: '10 seconds' +      ## The distinguished name of the container searched for objects in the directory information tree.      ## See also: additional_users_dn, additional_groups_dn.      # base_dn: 'dc=example,dc=com' diff --git a/internal/configuration/schema/authentication.go b/internal/configuration/schema/authentication.go index 4ebfb680f..010262abf 100644 --- a/internal/configuration/schema/authentication.go +++ b/internal/configuration/schema/authentication.go @@ -127,6 +127,8 @@ type AuthenticationBackendLDAP struct {  	StartTLS       bool          `koanf:"start_tls" json:"start_tls" jsonschema:"default=false,title=StartTLS" jsonschema_description:"Enables the use of StartTLS."`  	TLS            *TLS          `koanf:"tls" json:"tls" jsonschema:"title=TLS" jsonschema_description:"The LDAP directory server TLS connection properties."` +	Pooling AuthenticationBackendLDAPPooling `koanf:"pooling" json:"pooling" jsonschema:"title=Pooling" jsonschema_description:"The LDAP Connection Pooling properties."` +  	BaseDN string `koanf:"base_dn" json:"base_dn" jsonschema:"title=Base DN" jsonschema_description:"The base for all directory server operations."`  	AdditionalUsersDN string `koanf:"additional_users_dn" json:"additional_users_dn" jsonschema:"title=Additional User Base" jsonschema_description:"The base in addition to the Base DN for all directory server operations for users."` @@ -146,6 +148,13 @@ type AuthenticationBackendLDAP struct {  	Password string `koanf:"password" json:"password" jsonschema:"title=Password" jsonschema_description:"The password for LDAP authenticated binding."`  } +type AuthenticationBackendLDAPPooling struct { +	Enable  bool          `koanf:"enable" json:"enable" jsonschema:"title=Enable,default=false" jsonschema_description:"Enable LDAP connection pooling."` +	Count   int           `koanf:"count" json:"count" jsonschema:"title=Count,default=5" jsonschema_description:"The number of connections to keep open for LDAP connection pooling."` +	Retries int           `koanf:"retries" json:"retries" jsonschema:"title=Retries,default=2" jsonschema_description:"The number of attempts to retrieve a connection from the pool during the timeout."` +	Timeout time.Duration `koanf:"timeout" json:"timeout" jsonschema:"title=Timeout,default=10 seconds" jsonschema_description:"The duration of time to wait for a connection to become available in the connection pool."` +} +  // AuthenticationBackendLDAPAttributes represents the configuration related to LDAP server attributes.  type AuthenticationBackendLDAPAttributes struct {  	DistinguishedName string `koanf:"distinguished_name" json:"distinguished_name" jsonschema:"title=Attribute: Distinguished Name" jsonschema_description:"The directory server attribute which contains the distinguished name for all objects."` @@ -243,6 +252,11 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = Authenti  		GroupName:   ldapAttrCommonName,  	},  	Timeout: time.Second * 5, +	Pooling: AuthenticationBackendLDAPPooling{ +		Count:   5, +		Retries: 2, +		Timeout: time.Second * 10, +	},  	TLS: &TLS{  		MinimumVersion: TLSVersion{tls.VersionTLS12},  	}, diff --git a/internal/configuration/schema/keys.go b/internal/configuration/schema/keys.go index b065409ae..f2efaca93 100644 --- a/internal/configuration/schema/keys.go +++ b/internal/configuration/schema/keys.go @@ -197,6 +197,10 @@ var Keys = []string{  	"authentication_backend.ldap.tls.server_name",  	"authentication_backend.ldap.tls.private_key",  	"authentication_backend.ldap.tls.certificate_chain", +	"authentication_backend.ldap.pooling.enable", +	"authentication_backend.ldap.pooling.count", +	"authentication_backend.ldap.pooling.retries", +	"authentication_backend.ldap.pooling.timeout",  	"authentication_backend.ldap.base_dn",  	"authentication_backend.ldap.additional_users_dn",  	"authentication_backend.ldap.users_filter", diff --git a/internal/configuration/validator/authentication.go b/internal/configuration/validator/authentication.go index e53c7695d..c0dde8a26 100644 --- a/internal/configuration/validator/authentication.go +++ b/internal/configuration/validator/authentication.go @@ -349,6 +349,20 @@ func validateLDAPAuthenticationBackend(config *schema.AuthenticationBackend, val  		validator.Push(fmt.Errorf(errFmtLDAPAuthBackendTLSConfigInvalid, err))  	} +	if config.LDAP.Pooling.Enable { +		if config.LDAP.Pooling.Count < 1 { +			config.LDAP.Pooling.Count = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Pooling.Count +		} + +		if config.LDAP.Pooling.Retries < 1 { +			config.LDAP.Pooling.Retries = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Pooling.Retries +		} + +		if config.LDAP.Pooling.Timeout < 1 { +			config.LDAP.Pooling.Timeout = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Pooling.Timeout +		} +	} +  	if strings.Contains(config.LDAP.UsersFilter, "{0}") {  		validator.Push(fmt.Errorf(errFmtLDAPAuthBackendFilterReplacedPlaceholders, "users_filter", "{0}", "{input}"))  	} diff --git a/internal/configuration/validator/authentication_test.go b/internal/configuration/validator/authentication_test.go index c1ee3fe14..1fe5e2937 100644 --- a/internal/configuration/validator/authentication_test.go +++ b/internal/configuration/validator/authentication_test.go @@ -608,9 +608,22 @@ func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultImplementa  	suite.Equal(schema.LDAPImplementationCustom, suite.config.LDAP.Implementation) -	suite.Equal(suite.config.LDAP.Attributes.Username, schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Attributes.Username) +	suite.Equal(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Attributes.Username, suite.config.LDAP.Attributes.Username)  	suite.Len(suite.validator.Warnings(), 0)  	suite.Len(suite.validator.Errors(), 0) + +	suite.Equal(0, suite.config.LDAP.Pooling.Retries) +	suite.Equal(0, suite.config.LDAP.Pooling.Count) +	suite.Equal(time.Duration(0), suite.config.LDAP.Pooling.Timeout) +} + +func (suite *LDAPAuthenticationBackendSuite) TestShouldValidateDefaultPooling() { +	suite.config.LDAP.Pooling.Enable = true +	ValidateAuthenticationBackend(&suite.config, suite.validator) + +	suite.Equal(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Pooling.Retries, suite.config.LDAP.Pooling.Retries) +	suite.Equal(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Pooling.Count, suite.config.LDAP.Pooling.Count) +	suite.Equal(schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.Pooling.Timeout, suite.config.LDAP.Pooling.Timeout)  }  func (suite *LDAPAuthenticationBackendSuite) TestShouldRaiseErrorWhenImplementationIsInvalidMSAD() { diff --git a/internal/mocks/gen.go b/internal/mocks/gen.go index f7cab1b14..46c9446d2 100644 --- a/internal/mocks/gen.go +++ b/internal/mocks/gen.go @@ -10,7 +10,9 @@ package mocks  //go:generate mockgen -package mocks -destination duo_api.go -mock_names API=MockAPI github.com/authelia/authelia/v4/internal/duo API  //go:generate mockgen -package mocks -destination random.go -mock_names Provider=MockRandom github.com/authelia/authelia/v4/internal/random Provider -// Fosite Mocks. +// External Mocks. + +// Mocks for authelia.com/provider/oauth2.  //go:generate mockgen -package mocks -destination oauth2_client_credentials_grant_storage.go -mock_names Provider=MockClientCredentialsGrantStorage authelia.com/provider/oauth2/handler/oauth2 ClientCredentialsGrantStorage  //go:generate mockgen -package mocks -destination oauth2_token_revocation_storage.go -mock_names Provider=MockTokenRevocationStorage authelia.com/provider/oauth2/handler/oauth2 TokenRevocationStorage  //go:generate mockgen -package mocks -destination oauth2_access_token_strategy.go -mock_names Provider=MockAccessTokenStrategy authelia.com/provider/oauth2/handler/oauth2 AccessTokenStrategy diff --git a/internal/mocks/user_provider.go b/internal/mocks/user_provider.go index 81fa53483..b235d80b7 100644 --- a/internal/mocks/user_provider.go +++ b/internal/mocks/user_provider.go @@ -85,6 +85,20 @@ func (mr *MockUserProviderMockRecorder) GetDetailsExtended(username any) *gomock  	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDetailsExtended", reflect.TypeOf((*MockUserProvider)(nil).GetDetailsExtended), username)  } +// Shutdown mocks base method. +func (m *MockUserProvider) Shutdown() error { +	m.ctrl.T.Helper() +	ret := m.ctrl.Call(m, "Shutdown") +	ret0, _ := ret[0].(error) +	return ret0 +} + +// Shutdown indicates an expected call of Shutdown. +func (mr *MockUserProviderMockRecorder) Shutdown() *gomock.Call { +	mr.mock.ctrl.T.Helper() +	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Shutdown", reflect.TypeOf((*MockUserProvider)(nil).Shutdown)) +} +  // StartupCheck mocks base method.  func (m *MockUserProvider) StartupCheck() error {  	m.ctrl.T.Helper() diff --git a/internal/suites/ActiveDirectory/configuration.yml b/internal/suites/ActiveDirectory/configuration.yml index eb5ae0359..657bacc2c 100644 --- a/internal/suites/ActiveDirectory/configuration.yml +++ b/internal/suites/ActiveDirectory/configuration.yml @@ -43,6 +43,11 @@ session:  authentication_backend:    ldap:      address: 'ldap://sambaldap' +    pooling: +      enable: true +      count: 4 +      retries: 3 +      timeout: 10s      implementation: 'activedirectory'      tls:        skip_verify: true diff --git a/internal/suites/LDAP/configuration.yml b/internal/suites/LDAP/configuration.yml index a96976996..72876c0b5 100644 --- a/internal/suites/LDAP/configuration.yml +++ b/internal/suites/LDAP/configuration.yml @@ -43,6 +43,11 @@ session:  authentication_backend:    ldap:      address: 'ldaps://openldap' +    pooling: +      enable: true +      count: 4 +      retries: 3 +      timeout: 10s      tls:        skip_verify: true      base_dn: 'dc=example,dc=com' diff --git a/internal/utils/crypto.go b/internal/utils/crypto.go index 884aac5a2..9493ade5b 100644 --- a/internal/utils/crypto.go +++ b/internal/utils/crypto.go @@ -314,6 +314,10 @@ func IsX509PrivateKey(i any) bool {  // NewTLSConfig generates a tls.Config from a schema.TLS and a x509.CertPool.  func NewTLSConfig(config *schema.TLS, rootCAs *x509.CertPool) (tlsConfig *tls.Config) { +	if config == nil { +		return nil +	} +  	var certificates []tls.Certificate  	if config.PrivateKey != nil && config.CertificateChain.HasCertificates() {  | 
