diff options
| -rw-r--r-- | docs/content/en/reference/guides/ldap.md | 43 | ||||
| -rw-r--r-- | internal/authentication/const.go | 13 | ||||
| -rw-r--r-- | internal/authentication/ldap_user_provider.go | 40 | ||||
| -rw-r--r-- | internal/authentication/ldap_user_provider_startup.go | 12 | ||||
| -rw-r--r-- | internal/authentication/ldap_user_provider_test.go | 334 | ||||
| -rw-r--r-- | internal/configuration/schema/authentication.go | 4 | ||||
| -rw-r--r-- | internal/handlers/handler_verify_test.go | 16 | ||||
| -rw-r--r-- | internal/mocks/authelia_ctx.go | 25 | ||||
| -rw-r--r-- | internal/regulation/regulator_test.go | 3 | ||||
| -rw-r--r-- | internal/utils/clock.go | 20 | ||||
| -rw-r--r-- | internal/utils/const.go | 31 | ||||
| -rw-r--r-- | internal/utils/const_test.go | 5 | ||||
| -rw-r--r-- | internal/utils/time.go | 5 | ||||
| -rw-r--r-- | internal/utils/time_test.go | 8 | 
14 files changed, 315 insertions, 244 deletions
diff --git a/docs/content/en/reference/guides/ldap.md b/docs/content/en/reference/guides/ldap.md index 523cc258e..0d38b709d 100644 --- a/docs/content/en/reference/guides/ldap.md +++ b/docs/content/en/reference/guides/ldap.md @@ -60,12 +60,15 @@ search.  #### Users filter replacements -|       Placeholder        |  Phase  |              Replacement              | -|:------------------------:|:-------:|:-------------------------------------:| -|   {username_attribute}   | startup |   The configured username attribute   | -|     {mail_attribute}     | startup |     The configured mail attribute     | -| {display_name_attribute} | startup | The configured display name attribute | -|         {input}          | search  |   The input into the username field   | +|        Placeholder        |  Phase  |                                                  Replacement                                                   | +|:-------------------------:|:-------:|:--------------------------------------------------------------------------------------------------------------:| +|   {username_attribute}    | startup |                                       The configured username attribute                                        | +|     {mail_attribute}      | startup |                                         The configured mail attribute                                          | +| {display_name_attribute}  | startup |                                     The configured display name attribute                                      | +|          {input}          | search  |                                       The input into the username field                                        | +|  {date-time:generalized}  | search  |         The current UTC time formatted as a LDAP generalized time in the format of `20060102150405.0Z`         | +|  {date-time:unix-epoch}   | search  |                                   The current time formatted as a Unix epoch                                   | +| {date-time:msft-nt-epoch} | search  | The current time formatted as a Microsoft NT epoch which is used by some Microsoft Active Directory attributes |  #### Groups filter replacements @@ -92,16 +95,24 @@ Username column.  #### Filter defaults -The filters are probably the most important part to get correct when setting up LDAP. You want to exclude disabled -accounts. The active directory example has two attribute filters that accomplish this as an example (more examples would -be appreciated). The userAccountControl filter checks that the account is not disabled and the pwdLastSet makes sure that -value is not 0 which means the password requires changing at the next login. - -| Implementation  |                                                                          Users Filter                                                                           |                                Groups Filter                                 | -|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------:| -|     custom      |                                                                               N/A                                                                               |                                     N/A                                      | -| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) | -|     freeipa     |                         (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE)))                          |                  (&(member={dn})(objectClass=groupOfNames))                  | +The filters are probably the most important part to get correct when setting up LDAP. You want to exclude accounts under +the following conditions: + +- The account is disabled or locked: +  - The Active Directory implementation achieves this via the `(!(userAccountControl:1.2.840.113556.1.4.803:=2))` filter. +  - The FreeIPA implementation achieves this via the `(!(nsAccountLock=TRUE))` filter. +- Their password is expired: +  - The Active Directory implementation achieves this via the `(!(pwdLastSet=0))` filter. +  - The FreeIPA implementation achieves this via the `(krbPasswordExpiration>={date-time:generalized})` filter. +- Their account is expired: +  - The Active Directory implementation achieves this via the `(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:msft-nt-epoch}))` filter. +  - The FreeIPA implementation achieves this via the `(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized}))` filter. + +| Implementation  |                                                                                                                       Users Filter                                                                                                                        |                                Groups Filter                                 | +|:---------------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:----------------------------------------------------------------------------:| +|     custom      |                                                                                                                            N/A                                                                                                                            |                                     N/A                                      | +| activedirectory | (&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:msft-nt-epoch}))) | (&(member={dn})(|(sAMAccountType=268435456)(sAMAccountType=536870912))) | +|     freeipa     |   (&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))    |                  (&(member={dn})(objectClass=groupOfNames))                  |  ##### Microsoft Active Directory sAMAccountType diff --git a/internal/authentication/const.go b/internal/authentication/const.go index 48d31c186..4bf9e3cdb 100644 --- a/internal/authentication/const.go +++ b/internal/authentication/const.go @@ -70,9 +70,16 @@ const (  )  const ( -	ldapPlaceholderInput             = "{input}" -	ldapPlaceholderDistinguishedName = "{dn}" -	ldapPlaceholderUsername          = "{username}" +	ldapPlaceholderInput                        = "{input}" +	ldapPlaceholderDistinguishedName            = "{dn}" +	ldapPlaceholderUsername                     = "{username}" +	ldapPlaceholderDateTimeGeneralized          = "{date-time:generalized}" +	ldapPlaceholderDateTimeMicrosoftNTTimeEpoch = "{date-time:msft-nt-epoch}" +	ldapPlaceholderDateTimeUnixEpoch            = "{date-time:unix-epoch}" +) + +const ( +	ldapGeneralizedTimeDateTimeFormat = "20060102150405.0Z"  )  const ( diff --git a/internal/authentication/ldap_user_provider.go b/internal/authentication/ldap_user_provider.go index 65f635bf7..56ad08c6f 100644 --- a/internal/authentication/ldap_user_provider.go +++ b/internal/authentication/ldap_user_provider.go @@ -5,6 +5,7 @@ import (  	"crypto/x509"  	"fmt"  	"net" +	"strconv"  	"strings"  	"github.com/go-ldap/ldap/v3" @@ -23,15 +24,20 @@ type LDAPUserProvider struct {  	log       *logrus.Logger  	factory   LDAPClientFactory +	clock utils.Clock +  	disableResetPassword bool  	// Automatically detected LDAP features.  	features LDAPSupportedFeatures  	// Dynamically generated users values. -	usersBaseDN                 string -	usersAttributes             []string -	usersFilterReplacementInput bool +	usersBaseDN                                        string +	usersAttributes                                    []string +	usersFilterReplacementInput                        bool +	usersFilterReplacementDateTimeGeneralized          bool +	usersFilterReplacementDateTimeUnixEpoch            bool +	usersFilterReplacementDateTimeMicrosoftNTTimeEpoch bool  	// Dynamically generated groups values.  	groupsBaseDN                    string @@ -41,14 +47,15 @@ type LDAPUserProvider struct {  	groupsFilterReplacementDN       bool  } -// NewLDAPUserProvider creates a new instance of LDAPUserProvider. +// NewLDAPUserProvider creates a new instance of LDAPUserProvider with the ProductionLDAPClientFactory.  func NewLDAPUserProvider(config schema.AuthenticationBackend, certPool *x509.CertPool) (provider *LDAPUserProvider) { -	provider = newLDAPUserProvider(*config.LDAP, config.PasswordReset.Disable, certPool, nil) +	provider = NewLDAPUserProviderWithFactory(*config.LDAP, config.PasswordReset.Disable, certPool, NewProductionLDAPClientFactory())  	return provider  } -func newLDAPUserProvider(config schema.LDAPAuthenticationBackend, disableResetPassword bool, certPool *x509.CertPool, factory LDAPClientFactory) (provider *LDAPUserProvider) { +// NewLDAPUserProviderWithFactory creates a new instance of LDAPUserProvider with the specified LDAPClientFactory. +func NewLDAPUserProviderWithFactory(config schema.LDAPAuthenticationBackend, disableResetPassword bool, certPool *x509.CertPool, factory LDAPClientFactory) (provider *LDAPUserProvider) {  	if config.TLS == nil {  		config.TLS = schema.DefaultLDAPAuthenticationBackendConfigurationImplementationCustom.TLS  	} @@ -74,6 +81,7 @@ func newLDAPUserProvider(config schema.LDAPAuthenticationBackend, disableResetPa  		log:                  logging.Logger(),  		factory:              factory,  		disableResetPassword: disableResetPassword, +		clock:                &utils.RealClock{},  	}  	provider.parseDynamicUsersConfiguration() @@ -394,12 +402,24 @@ func (p *LDAPUserProvider) getUserProfile(client LDAPClient, username string) (p  	return &userProfile, nil  } -func (p *LDAPUserProvider) resolveUsersFilter(username string) (filter string) { +func (p *LDAPUserProvider) resolveUsersFilter(input string) (filter string) {  	filter = p.config.UsersFilter  	if p.usersFilterReplacementInput {  		// The {input} placeholder is replaced by the username input. -		filter = strings.ReplaceAll(filter, ldapPlaceholderInput, ldapEscape(username)) +		filter = strings.ReplaceAll(filter, ldapPlaceholderInput, ldapEscape(input)) +	} + +	if p.usersFilterReplacementDateTimeGeneralized { +		filter = strings.ReplaceAll(filter, ldapPlaceholderDateTimeGeneralized, p.clock.Now().UTC().Format(ldapGeneralizedTimeDateTimeFormat)) +	} + +	if p.usersFilterReplacementDateTimeUnixEpoch { +		filter = strings.ReplaceAll(filter, ldapPlaceholderDateTimeUnixEpoch, strconv.Itoa(int(p.clock.Now().Unix()))) +	} + +	if p.usersFilterReplacementDateTimeMicrosoftNTTimeEpoch { +		filter = strings.ReplaceAll(filter, ldapPlaceholderDateTimeMicrosoftNTTimeEpoch, strconv.Itoa(int(utils.UnixNanoTimeToMicrosoftNTEpoch(p.clock.Now().UnixNano()))))  	}  	p.log.Tracef("Detected user filter is %s", filter) @@ -407,12 +427,12 @@ func (p *LDAPUserProvider) resolveUsersFilter(username string) (filter string) {  	return filter  } -func (p *LDAPUserProvider) resolveGroupsFilter(username string, profile *ldapUserProfile) (filter string) { +func (p *LDAPUserProvider) resolveGroupsFilter(input string, profile *ldapUserProfile) (filter string) {  	filter = p.config.GroupsFilter  	if p.groupsFilterReplacementInput {  		// The {input} placeholder is replaced by the users username input. -		filter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderInput, ldapEscape(username)) +		filter = strings.ReplaceAll(p.config.GroupsFilter, ldapPlaceholderInput, ldapEscape(input))  	}  	if profile != nil { diff --git a/internal/authentication/ldap_user_provider_startup.go b/internal/authentication/ldap_user_provider_startup.go index a3a2e760a..edf668587 100644 --- a/internal/authentication/ldap_user_provider_startup.go +++ b/internal/authentication/ldap_user_provider_startup.go @@ -120,6 +120,18 @@ func (p *LDAPUserProvider) parseDynamicUsersConfiguration() {  		p.usersFilterReplacementInput = true  	} +	if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeGeneralized) { +		p.usersFilterReplacementDateTimeGeneralized = true +	} + +	if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeUnixEpoch) { +		p.usersFilterReplacementDateTimeUnixEpoch = true +	} + +	if strings.Contains(p.config.UsersFilter, ldapPlaceholderDateTimeMicrosoftNTTimeEpoch) { +		p.usersFilterReplacementDateTimeMicrosoftNTTimeEpoch = true +	} +  	p.log.Tracef("Detected user filter replacements that need to be resolved per lookup are: %s=%v",  		ldapPlaceholderInput, p.usersFilterReplacementInput)  } diff --git a/internal/authentication/ldap_user_provider_test.go b/internal/authentication/ldap_user_provider_test.go index d28d05e9c..2b929683e 100644 --- a/internal/authentication/ldap_user_provider_test.go +++ b/internal/authentication/ldap_user_provider_test.go @@ -4,6 +4,7 @@ import (  	"errors"  	"fmt"  	"testing" +	"time"  	"github.com/go-ldap/ldap/v3"  	"github.com/golang/mock/gomock" @@ -22,7 +23,7 @@ func TestShouldCreateRawConnectionWhenSchemeIsLDAP(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:      "ldap://127.0.0.1:389",  			User:     "cn=admin,dc=example,dc=com", @@ -42,7 +43,7 @@ func TestShouldCreateRawConnectionWhenSchemeIsLDAP(t *testing.T) {  	gomock.InOrder(dialURL, connBind) -	_, err := ldapClient.connect() +	_, err := provider.connect()  	require.NoError(t, err)  } @@ -54,7 +55,7 @@ func TestShouldCreateTLSConnectionWhenSchemeIsLDAPS(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:      "ldaps://127.0.0.1:389",  			User:     "cn=admin,dc=example,dc=com", @@ -74,7 +75,7 @@ func TestShouldCreateTLSConnectionWhenSchemeIsLDAPS(t *testing.T) {  	gomock.InOrder(dialURL, connBind) -	_, err := ldapClient.connect() +	_, err := provider.connect()  	require.NoError(t, err)  } @@ -104,7 +105,7 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:          "ldaps://127.0.0.1:389",  			GroupsFilter: "(|(member={dn})(uid={username})(uid={input}))", @@ -120,10 +121,10 @@ func TestEscapeSpecialCharsInGroupsFilter(t *testing.T) {  		Emails:      []string{"john.doe@authelia.com"},  	} -	filter := ldapClient.resolveGroupsFilter("john", &profile) +	filter := provider.resolveGroupsFilter("john", &profile)  	assert.Equal(t, "(|(member=cn=john \\28external\\29,dc=example,dc=com)(uid=john)(uid=john))", filter) -	filter = ldapClient.resolveGroupsFilter("john#=(abc,def)", &profile) +	filter = provider.resolveGroupsFilter("john#=(abc,def)", &profile)  	assert.Equal(t, "(|(member=cn=john \\28external\\29,dc=example,dc=com)(uid=john)(uid=john\\#\\=\\28abc\\,def\\29))", filter)  } @@ -162,7 +163,7 @@ func TestShouldCheckLDAPServerExtensions(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -210,14 +211,14 @@ func TestShouldCheckLDAPServerExtensions(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	assert.NoError(t, err) -	assert.True(t, ldapClient.features.Extensions.PwdModifyExOp) -	assert.True(t, ldapClient.features.Extensions.TLS) +	assert.True(t, provider.features.Extensions.PwdModifyExOp) +	assert.True(t, provider.features.Extensions.TLS) -	assert.False(t, ldapClient.features.ControlTypes.MsftPwdPolHints) -	assert.False(t, ldapClient.features.ControlTypes.MsftPwdPolHintsDeprecated) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated)  }  func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntry(t *testing.T) { @@ -227,7 +228,7 @@ func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntry(t  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -276,14 +277,14 @@ func TestShouldNotCheckLDAPServerExtensionsWhenRootDSEReturnsMoreThanOneEntry(t  	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	assert.NoError(t, err) -	assert.False(t, ldapClient.features.Extensions.PwdModifyExOp) -	assert.False(t, ldapClient.features.Extensions.TLS) +	assert.False(t, provider.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.TLS) -	assert.False(t, ldapClient.features.ControlTypes.MsftPwdPolHints) -	assert.False(t, ldapClient.features.ControlTypes.MsftPwdPolHintsDeprecated) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated)  }  func TestShouldCheckLDAPServerControlTypes(t *testing.T) { @@ -293,7 +294,7 @@ func TestShouldCheckLDAPServerControlTypes(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -341,14 +342,14 @@ func TestShouldCheckLDAPServerControlTypes(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	assert.NoError(t, err) -	assert.False(t, ldapClient.features.Extensions.PwdModifyExOp) -	assert.False(t, ldapClient.features.Extensions.TLS) +	assert.False(t, provider.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.TLS) -	assert.True(t, ldapClient.features.ControlTypes.MsftPwdPolHints) -	assert.True(t, ldapClient.features.ControlTypes.MsftPwdPolHintsDeprecated) +	assert.True(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.True(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated)  }  func TestShouldNotEnablePasswdModifyExtensionOrControlTypes(t *testing.T) { @@ -358,7 +359,7 @@ func TestShouldNotEnablePasswdModifyExtensionOrControlTypes(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -406,14 +407,14 @@ func TestShouldNotEnablePasswdModifyExtensionOrControlTypes(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	assert.NoError(t, err) -	assert.False(t, ldapClient.features.Extensions.PwdModifyExOp) -	assert.False(t, ldapClient.features.Extensions.TLS) +	assert.False(t, provider.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.TLS) -	assert.False(t, ldapClient.features.ControlTypes.MsftPwdPolHints) -	assert.False(t, ldapClient.features.ControlTypes.MsftPwdPolHintsDeprecated) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHints) +	assert.False(t, provider.features.ControlTypes.MsftPwdPolHintsDeprecated)  }  func TestShouldReturnCheckServerConnectError(t *testing.T) { @@ -423,7 +424,7 @@ func TestShouldReturnCheckServerConnectError(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -443,10 +444,10 @@ func TestShouldReturnCheckServerConnectError(t *testing.T) {  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()).  		Return(mockClient, errors.New("could not connect")) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	assert.EqualError(t, err, "dial failed with error: could not connect") -	assert.False(t, ldapClient.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.PwdModifyExOp)  }  func TestShouldReturnCheckServerSearchError(t *testing.T) { @@ -456,7 +457,7 @@ func TestShouldReturnCheckServerSearchError(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -488,10 +489,10 @@ func TestShouldReturnCheckServerSearchError(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchOIDs, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	assert.EqualError(t, err, "error occurred during RootDSE search: could not perform the search") -	assert.False(t, ldapClient.features.Extensions.PwdModifyExOp) +	assert.False(t, provider.features.Extensions.PwdModifyExOp)  }  type SearchRequestMatcher struct { @@ -518,7 +519,7 @@ func TestShouldEscapeUserInput(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -540,7 +541,7 @@ func TestShouldEscapeUserInput(t *testing.T) {  		Search(NewSearchRequestMatcher("(|(uid=john\\=abc)(mail=john\\=abc))")).  		Return(&ldap.SearchResult{}, nil) -	_, err := ldapClient.getUserProfile(mockClient, "john=abc") +	_, err := provider.getUserProfile(mockClient, "john=abc")  	require.Error(t, err)  	assert.EqualError(t, err, "user not found")  } @@ -552,7 +553,7 @@ func TestShouldReturnEmailWhenAttributeSameAsUsername(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -568,7 +569,7 @@ func TestShouldReturnEmailWhenAttributeSameAsUsername(t *testing.T) {  		nil,  		mockFactory) -	assert.Equal(t, []string{"mail", "displayName"}, ldapClient.usersAttributes) +	assert.Equal(t, []string{"mail", "displayName"}, provider.usersAttributes)  	dialURL := mockFactory.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). @@ -600,10 +601,10 @@ func TestShouldReturnEmailWhenAttributeSameAsUsername(t *testing.T) {  	gomock.InOrder(dialURL, bind, search) -	client, err := ldapClient.connect() +	client, err := provider.connect()  	assert.NoError(t, err) -	profile, err := ldapClient.getUserProfile(client, "john@example.com") +	profile, err := provider.getUserProfile(client, "john@example.com")  	assert.NoError(t, err)  	require.NotNil(t, profile) @@ -623,7 +624,7 @@ func TestShouldReturnUsernameAndBlankDisplayNameWhenAttributesTheSame(t *testing  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -639,7 +640,7 @@ func TestShouldReturnUsernameAndBlankDisplayNameWhenAttributesTheSame(t *testing  		nil,  		mockFactory) -	assert.Equal(t, []string{"uid", "mail"}, ldapClient.usersAttributes) +	assert.Equal(t, []string{"uid", "mail"}, provider.usersAttributes)  	dialURL := mockFactory.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). @@ -671,10 +672,10 @@ func TestShouldReturnUsernameAndBlankDisplayNameWhenAttributesTheSame(t *testing  	gomock.InOrder(dialURL, bind, search) -	client, err := ldapClient.connect() +	client, err := provider.connect()  	assert.NoError(t, err) -	profile, err := ldapClient.getUserProfile(client, "john@example.com") +	profile, err := provider.getUserProfile(client, "john@example.com")  	assert.NoError(t, err)  	require.NotNil(t, profile) @@ -694,7 +695,7 @@ func TestShouldReturnBlankEmailAndDisplayNameWhenAttrsLenZero(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -710,7 +711,7 @@ func TestShouldReturnBlankEmailAndDisplayNameWhenAttrsLenZero(t *testing.T) {  		nil,  		mockFactory) -	assert.Equal(t, []string{"uid", "mail", "displayName"}, ldapClient.usersAttributes) +	assert.Equal(t, []string{"uid", "mail", "displayName"}, provider.usersAttributes)  	dialURL := mockFactory.EXPECT().  		DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). @@ -746,10 +747,10 @@ func TestShouldReturnBlankEmailAndDisplayNameWhenAttrsLenZero(t *testing.T) {  	gomock.InOrder(dialURL, bind, search) -	client, err := ldapClient.connect() +	client, err := provider.connect()  	assert.NoError(t, err) -	profile, err := ldapClient.getUserProfile(client, "john@example.com") +	profile, err := provider.getUserProfile(client, "john@example.com")  	assert.NoError(t, err)  	require.NotNil(t, profile) @@ -768,7 +769,7 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -785,15 +786,15 @@ func TestShouldCombineUsernameFilterAndUsersFilter(t *testing.T) {  		nil,  		mockFactory) -	assert.Equal(t, []string{"uid", "mail", "displayName"}, ldapClient.usersAttributes) +	assert.Equal(t, []string{"uid", "mail", "displayName"}, provider.usersAttributes) -	assert.True(t, ldapClient.usersFilterReplacementInput) +	assert.True(t, provider.usersFilterReplacementInput)  	mockClient.EXPECT().  		Search(NewSearchRequestMatcher("(&(uid=john)(&(objectCategory=person)(objectClass=user)))")).  		Return(&ldap.SearchResult{}, nil) -	_, err := ldapClient.getUserProfile(mockClient, "john") +	_, err := provider.getUserProfile(mockClient, "john")  	require.Error(t, err)  	assert.EqualError(t, err, "user not found")  } @@ -819,7 +820,7 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -876,7 +877,7 @@ func TestShouldNotCrashWhenGroupsAreNotRetrievedFromLDAP(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) -	details, err := ldapClient.GetDetails("john") +	details, err := provider.GetDetails("john")  	require.NoError(t, err)  	assert.ElementsMatch(t, details.Groups, []string{}) @@ -892,7 +893,7 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:               "ldap://127.0.0.1:389",  			User:              "cn=admin,dc=example,dc=com", @@ -938,7 +939,7 @@ func TestShouldNotCrashWhenEmailsAreNotRetrievedFromLDAP(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) -	details, err := ldapClient.GetDetails("john") +	details, err := provider.GetDetails("john")  	require.NoError(t, err)  	assert.ElementsMatch(t, details.Groups, []string{"group1", "group2"}) @@ -953,7 +954,7 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -1009,7 +1010,7 @@ func TestShouldReturnUsernameFromLDAP(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchProfile, searchGroups, connClose) -	details, err := ldapClient.GetDetails("john") +	details, err := provider.GetDetails("john")  	require.NoError(t, err)  	assert.ElementsMatch(t, details.Groups, []string{"group1", "group2"}) @@ -1026,7 +1027,7 @@ func TestShouldReturnUsernameFromLDAPWithReferrals(t *testing.T) {  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -1100,7 +1101,7 @@ func TestShouldReturnUsernameFromLDAPWithReferrals(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchProfile, dialURLReferral, connBindReferral, searchProfileReferral, connCloseReferral, searchGroups, connClose) -	details, err := ldapClient.GetDetails("john") +	details, err := provider.GetDetails("john")  	require.NoError(t, err)  	assert.ElementsMatch(t, details.Groups, []string{"group1", "group2"}) @@ -1118,7 +1119,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndResult(t *testing.T)  	mockClientReferral := NewMockLDAPClient(ctrl)  	mockClientReferralAlt := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -1226,7 +1227,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsInErrorAndResult(t *testing.T)  	gomock.InOrder(dialURL, connBind, searchProfile, dialURLReferral, connBindReferral, searchProfileReferral, connCloseReferral, dialURLReferralAlt, connBindReferralAlt, searchProfileReferralAlt, connCloseReferralAlt, searchGroups, connClose) -	details, err := ldapClient.GetDetails("john") +	details, err := provider.GetDetails("john")  	require.NoError(t, err)  	assert.ElementsMatch(t, details.Groups, []string{"group1", "group2"}) @@ -1243,7 +1244,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsErr(t *testing.T) {  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -1314,7 +1315,7 @@ func TestShouldReturnUsernameFromLDAPWithReferralsErr(t *testing.T) {  	gomock.InOrder(dialURL, connBind, searchProfile, dialURLReferral, connBindReferral, searchProfileReferral, connCloseReferral, searchGroups, connClose) -	details, err := ldapClient.GetDetails("john") +	details, err := provider.GetDetails("john")  	require.NoError(t, err)  	assert.ElementsMatch(t, details.Groups, []string{"group1", "group2"}) @@ -1330,7 +1331,7 @@ func TestShouldNotUpdateUserPasswordConnect(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -1383,10 +1384,10 @@ func TestShouldNotUpdateUserPasswordConnect(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	assert.EqualError(t, err, "unable to update password. Cause: dial failed with error: tcp timeout")  } @@ -1397,7 +1398,7 @@ func TestShouldNotUpdateUserPasswordGetDetails(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -1460,10 +1461,10 @@ func TestShouldNotUpdateUserPasswordGetDetails(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	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")  } @@ -1474,7 +1475,7 @@ func TestShouldUpdateUserPassword(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -1567,10 +1568,10 @@ func TestShouldUpdateUserPassword(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	require.NoError(t, err)  } @@ -1581,7 +1582,7 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "activedirectory",  			URL:                  "ldap://127.0.0.1:389", @@ -1676,10 +1677,10 @@ func TestShouldUpdateUserPasswordMSAD(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	require.NoError(t, err)  } @@ -1691,7 +1692,7 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "activedirectory",  			URL:                  "ldap://127.0.0.1:389", @@ -1805,10 +1806,10 @@ func TestShouldUpdateUserPasswordMSADWithReferrals(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, dialURLReferral, connBindReferral, modifyReferral, connCloseReferral, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	require.NoError(t, err)  } @@ -1819,7 +1820,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "activedirectory",  			URL:                  "ldap://127.0.0.1:389", @@ -1923,10 +1924,10 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralConnectErr(t *test  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, dialURLReferral, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	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")  } @@ -1938,7 +1939,7 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "activedirectory",  			URL:                  "ldap://127.0.0.1:389", @@ -2056,10 +2057,10 @@ func TestShouldUpdateUserPasswordMSADWithReferralsWithReferralModifyErr(t *testi  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, dialURLReferral, connBindReferral, modifyReferral, connCloseReferral, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	assert.EqualError(t, err, "unable to update password. Cause: error occurred performing modify on referred LDAP server 'ldap://192.168.0.1': LDAP Result Code 51 \"Busy\": error occurred. Original Error: LDAP Result Code 10 \"Referral\": error occurred")  } @@ -2070,7 +2071,7 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "activedirectory",  			URL:                  "ldap://127.0.0.1:389", @@ -2170,10 +2171,10 @@ func TestShouldUpdateUserPasswordMSADWithoutReferrals(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, modify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	assert.EqualError(t, err, "unable to update password. Cause: LDAP Result Code 10 \"Referral\": error occurred")  } @@ -2184,7 +2185,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -2276,10 +2277,10 @@ func TestShouldUpdateUserPasswordPasswdModifyExtension(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	require.NoError(t, err)  } @@ -2291,7 +2292,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferrals(t *testing.T  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -2404,10 +2405,10 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferrals(t *testing.T  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, dialURLReferral, connBindReferral, passwdModifyReferral, connCloseReferral, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	require.NoError(t, err)  } @@ -2418,7 +2419,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithoutReferrals(t *testin  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -2517,10 +2518,10 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithoutReferrals(t *testin  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	assert.EqualError(t, err, "unable to update password. Cause: LDAP Result Code 10 \"Referral\": error occurred")  } @@ -2531,7 +2532,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralConne  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -2634,10 +2635,10 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralConne  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, dialURLReferral, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	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")  } @@ -2649,7 +2650,7 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralPassw  	mockClient := NewMockLDAPClient(ctrl)  	mockClientReferral := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -2766,10 +2767,10 @@ func TestShouldUpdateUserPasswordPasswdModifyExtensionWithReferralsReferralPassw  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, dialURLReferral, connBindReferral, passwdModifyReferral, connCloseReferral, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	assert.EqualError(t, err, "unable to update password. Cause: error occurred performing password modify on referred LDAP server 'ldap://192.168.0.1': LDAP Result Code 51 \"Busy\": too busy. Original Error: LDAP Result Code 10 \"Referral\": error occurred")  } @@ -2780,7 +2781,7 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHints(t *testing  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "activedirectory",  			URL:                  "ldap://127.0.0.1:389", @@ -2877,10 +2878,10 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHints(t *testing  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	assert.NoError(t, err)  } @@ -2891,7 +2892,7 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHintsDeprecated(  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "activedirectory",  			URL:                  "ldap://127.0.0.1:389", @@ -2988,10 +2989,10 @@ func TestShouldUpdateUserPasswordActiveDirectoryWithServerPolicyHintsDeprecated(  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	require.NoError(t, err)  } @@ -3002,7 +3003,7 @@ func TestShouldUpdateUserPasswordActiveDirectory(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "activedirectory",  			URL:                  "ldap://127.0.0.1:389", @@ -3099,10 +3100,10 @@ func TestShouldUpdateUserPasswordActiveDirectory(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	require.NoError(t, err)  } @@ -3113,7 +3114,7 @@ func TestShouldUpdateUserPasswordBasic(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			Implementation:       "custom",  			URL:                  "ldap://127.0.0.1:389", @@ -3207,10 +3208,10 @@ func TestShouldUpdateUserPasswordBasic(t *testing.T) {  	gomock.InOrder(dialURLOIDs, connBindOIDs, searchOIDs, connCloseOIDs, dialURL, connBind, searchProfile, passwdModify, connClose) -	err := ldapClient.StartupCheck() +	err := provider.StartupCheck()  	require.NoError(t, err) -	err = ldapClient.UpdatePassword("john", "password") +	err = provider.UpdatePassword("john", "password")  	require.NoError(t, err)  } @@ -3221,7 +3222,7 @@ func TestShouldReturnErrorWhenMultipleUsernameAttributes(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3271,10 +3272,10 @@ func TestShouldReturnErrorWhenMultipleUsernameAttributes(t *testing.T) {  	gomock.InOrder(dialURL, bind, search) -	client, err := ldapClient.connect() +	client, err := provider.connect()  	assert.NoError(t, err) -	profile, err := ldapClient.getUserProfile(client, "john") +	profile, err := provider.getUserProfile(client, "john")  	assert.Nil(t, profile)  	assert.EqualError(t, err, "user 'john' has 2 values for for attribute 'uid' but the attribute must be a single value attribute") @@ -3287,7 +3288,7 @@ func TestShouldReturnErrorWhenZeroUsernameAttributes(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3337,10 +3338,10 @@ func TestShouldReturnErrorWhenZeroUsernameAttributes(t *testing.T) {  	gomock.InOrder(dialURL, bind, search) -	client, err := ldapClient.connect() +	client, err := provider.connect()  	assert.NoError(t, err) -	profile, err := ldapClient.getUserProfile(client, "john") +	profile, err := provider.getUserProfile(client, "john")  	assert.Nil(t, profile)  	assert.EqualError(t, err, "user 'john' must have value for attribute 'uid'") @@ -3353,7 +3354,7 @@ func TestShouldReturnErrorWhenUsernameAttributeNotReturned(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3399,10 +3400,10 @@ func TestShouldReturnErrorWhenUsernameAttributeNotReturned(t *testing.T) {  	gomock.InOrder(dialURL, bind, search) -	client, err := ldapClient.connect() +	client, err := provider.connect()  	assert.NoError(t, err) -	profile, err := ldapClient.getUserProfile(client, "john") +	profile, err := provider.getUserProfile(client, "john")  	assert.Nil(t, profile)  	assert.EqualError(t, err, "user 'john' must have value for attribute 'uid'") @@ -3415,7 +3416,7 @@ func TestShouldReturnErrorWhenMultipleUsersFound(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3482,10 +3483,10 @@ func TestShouldReturnErrorWhenMultipleUsersFound(t *testing.T) {  	gomock.InOrder(dialURL, bind, search) -	client, err := ldapClient.connect() +	client, err := provider.connect()  	assert.NoError(t, err) -	profile, err := ldapClient.getUserProfile(client, "john") +	profile, err := provider.getUserProfile(client, "john")  	assert.Nil(t, profile)  	assert.EqualError(t, err, "there were 2 users found when searching for 'john' but there should only be 1") @@ -3498,7 +3499,7 @@ func TestShouldReturnErrorWhenNoDN(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3548,10 +3549,10 @@ func TestShouldReturnErrorWhenNoDN(t *testing.T) {  	gomock.InOrder(dialURL, bind, search) -	client, err := ldapClient.connect() +	client, err := provider.connect()  	assert.NoError(t, err) -	profile, err := ldapClient.getUserProfile(client, "john") +	profile, err := provider.getUserProfile(client, "john")  	assert.Nil(t, profile)  	assert.EqualError(t, err, "user 'john' must have a distinguished name but the result returned an empty distinguished name") @@ -3564,7 +3565,7 @@ func TestShouldCheckValidUserPassword(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3619,7 +3620,7 @@ func TestShouldCheckValidUserPassword(t *testing.T) {  		mockClient.EXPECT().Close().Times(2),  	) -	valid, err := ldapClient.CheckUserPassword("john", "password") +	valid, err := provider.CheckUserPassword("john", "password")  	assert.True(t, valid)  	require.NoError(t, err) @@ -3632,7 +3633,7 @@ func TestShouldNotCheckValidUserPasswordWithConnectError(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3658,7 +3659,7 @@ func TestShouldNotCheckValidUserPasswordWithConnectError(t *testing.T) {  	gomock.InOrder(dialURL, bind, mockClient.EXPECT().Close()) -	valid, err := ldapClient.CheckUserPassword("john", "password") +	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") @@ -3671,7 +3672,7 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3726,7 +3727,7 @@ func TestShouldCheckInvalidUserPassword(t *testing.T) {  		mockClient.EXPECT().Close().Times(2),  	) -	valid, err := ldapClient.CheckUserPassword("john", "password") +	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") @@ -3739,7 +3740,7 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3765,7 +3766,7 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {  		Return(nil)  	connStartTLS := mockClient.EXPECT(). -		StartTLS(ldapClient.tlsConfig) +		StartTLS(provider.tlsConfig)  	connClose := mockClient.EXPECT().Close() @@ -3799,7 +3800,7 @@ func TestShouldCallStartTLSWhenEnabled(t *testing.T) {  	gomock.InOrder(dialURL, connStartTLS, connBind, searchProfile, searchGroups, connClose) -	details, err := ldapClient.GetDetails("john") +	details, err := provider.GetDetails("john")  	require.NoError(t, err)  	assert.ElementsMatch(t, details.Groups, []string{}) @@ -3814,7 +3815,7 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3822,7 +3823,7 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {  			UsernameAttribute:    "uid",  			MailAttribute:        "mail",  			DisplayNameAttribute: "displayName", -			UsersFilter:          "(&(|({username_attribute}={input})({mail_attribute}={input})({display_name_attribute}={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))", +			UsersFilter:          "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:msft-nt-epoch})(accountExpires>={date-time:generalized})))",  			GroupsFilter:         "(&(|(member={dn})(member={input})(member={username}))(objectClass=group))",  			AdditionalUsersDN:    "ou=users",  			AdditionalGroupsDN:   "ou=groups", @@ -3833,16 +3834,27 @@ func TestShouldParseDynamicConfiguration(t *testing.T) {  		nil,  		mockFactory) -	assert.True(t, ldapClient.groupsFilterReplacementInput) -	assert.True(t, ldapClient.groupsFilterReplacementUsername) -	assert.True(t, ldapClient.groupsFilterReplacementDN) +	clock := &utils.TestingClock{} -	assert.True(t, ldapClient.usersFilterReplacementInput) +	provider.clock = clock -	assert.Equal(t, "(&(|(uid={input})(mail={input})(displayName={input}))(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=2)(!pwdLastSet=0))", ldapClient.config.UsersFilter) -	assert.Equal(t, "(&(|(member={dn})(member={input})(member={username}))(objectClass=group))", ldapClient.config.GroupsFilter) -	assert.Equal(t, "ou=users,dc=example,dc=com", ldapClient.usersBaseDN) -	assert.Equal(t, "ou=groups,dc=example,dc=com", ldapClient.groupsBaseDN) +	clock.Set(time.Unix(1670250519, 0)) + +	assert.True(t, provider.groupsFilterReplacementInput) +	assert.True(t, provider.groupsFilterReplacementUsername) +	assert.True(t, provider.groupsFilterReplacementDN) + +	assert.True(t, provider.usersFilterReplacementInput) +	assert.True(t, provider.usersFilterReplacementDateTimeGeneralized) +	assert.True(t, provider.usersFilterReplacementDateTimeMicrosoftNTTimeEpoch) + +	assert.Equal(t, "(&(|(uid={input})(mail={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:msft-nt-epoch})(accountExpires>={date-time:generalized})))", provider.config.UsersFilter) +	assert.Equal(t, "(&(|(member={dn})(member={input})(member={username}))(objectClass=group))", provider.config.GroupsFilter) +	assert.Equal(t, "ou=users,dc=example,dc=com", provider.usersBaseDN) +	assert.Equal(t, "ou=groups,dc=example,dc=com", provider.groupsBaseDN) + +	assert.Equal(t, "(&(|(uid=test@example.com)(mail=test@example.com))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>=133147241190000000)(accountExpires>=20221205142839.0Z)))", provider.resolveUsersFilter("test@example.com")) +	assert.Equal(t, "(&(|(member=cn=admin,dc=example,dc=com)(member=test@example.com)(member=test))(objectClass=group))", provider.resolveGroupsFilter("test@example.com", &ldapUserProfile{Username: "test", DN: "cn=admin,dc=example,dc=com"}))  }  func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T) { @@ -3852,7 +3864,7 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldap://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3872,9 +3884,9 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T  		nil,  		mockFactory) -	assert.False(t, ldapClient.groupsFilterReplacementInput) -	assert.False(t, ldapClient.groupsFilterReplacementUsername) -	assert.False(t, ldapClient.groupsFilterReplacementDN) +	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()). @@ -3885,7 +3897,7 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T  		Return(nil)  	connStartTLS := mockClient.EXPECT(). -		StartTLS(ldapClient.tlsConfig) +		StartTLS(provider.tlsConfig)  	connClose := mockClient.EXPECT().Close() @@ -3919,7 +3931,7 @@ func TestShouldCallStartTLSWithInsecureSkipVerifyWhenSkipVerifyTrue(t *testing.T  	gomock.InOrder(dialURL, connStartTLS, connBind, searchProfile, searchGroups, connClose) -	details, err := ldapClient.GetDetails("john") +	details, err := provider.GetDetails("john")  	require.NoError(t, err)  	assert.ElementsMatch(t, details.Groups, []string{}) @@ -3935,7 +3947,7 @@ func TestShouldReturnLDAPSAlreadySecuredWhenStartTLSAttempted(t *testing.T) {  	mockFactory := NewMockLDAPClientFactory(ctrl)  	mockClient := NewMockLDAPClient(ctrl) -	ldapClient := newLDAPUserProvider( +	provider := NewLDAPUserProviderWithFactory(  		schema.LDAPAuthenticationBackend{  			URL:                  "ldaps://127.0.0.1:389",  			User:                 "cn=admin,dc=example,dc=com", @@ -3960,11 +3972,11 @@ func TestShouldReturnLDAPSAlreadySecuredWhenStartTLSAttempted(t *testing.T) {  		Return(mockClient, nil)  	connStartTLS := mockClient.EXPECT(). -		StartTLS(ldapClient.tlsConfig). +		StartTLS(provider.tlsConfig).  		Return(errors.New("LDAP Result Code 200 \"Network Error\": ldap: already encrypted"))  	gomock.InOrder(dialURL, connStartTLS, mockClient.EXPECT().Close()) -	_, err := ldapClient.GetDetails("john") +	_, err := provider.GetDetails("john")  	assert.EqualError(t, err, "starttls failed with error: LDAP Result Code 200 \"Network Error\": ldap: already encrypted")  } diff --git a/internal/configuration/schema/authentication.go b/internal/configuration/schema/authentication.go index 1d7bf181b..d830071ac 100644 --- a/internal/configuration/schema/authentication.go +++ b/internal/configuration/schema/authentication.go @@ -187,7 +187,7 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationCustom = LDAPAuth  // DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory represents the default LDAP config for the LDAPImplementationActiveDirectory Implementation.  var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory = LDAPAuthenticationBackend{ -	UsersFilter:          "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0)))", +	UsersFilter:          "(&(|({username_attribute}={input})({mail_attribute}={input}))(sAMAccountType=805306368)(!(userAccountControl:1.2.840.113556.1.4.803:=2))(!(pwdLastSet=0))(|(!(accountExpires=*))(accountExpires=0)(accountExpires>={date-time:msft-nt-epoch})))",  	UsernameAttribute:    "sAMAccountName",  	MailAttribute:        ldapAttrMail,  	DisplayNameAttribute: ldapAttrDisplayName, @@ -201,7 +201,7 @@ var DefaultLDAPAuthenticationBackendConfigurationImplementationActiveDirectory =  // DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA represents the default LDAP config for the LDAPImplementationFreeIPA Implementation.  var DefaultLDAPAuthenticationBackendConfigurationImplementationFreeIPA = LDAPAuthenticationBackend{ -	UsersFilter:          "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE)))", +	UsersFilter:          "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person)(!(nsAccountLock=TRUE))(krbPasswordExpiration>={date-time:generalized})(|(!(krbPrincipalExpiration=*))(krbPrincipalExpiration>={date-time:generalized})))",  	UsernameAttribute:    ldapAttrUserID,  	MailAttribute:        ldapAttrMail,  	DisplayNameAttribute: ldapAttrDisplayName, diff --git a/internal/handlers/handler_verify_test.go b/internal/handlers/handler_verify_test.go index a0ef56858..518ac7ec0 100644 --- a/internal/handlers/handler_verify_test.go +++ b/internal/handlers/handler_verify_test.go @@ -702,7 +702,7 @@ func TestShouldDestroySessionWhenInactiveForTooLong(t *testing.T) {  	mock := mocks.NewMockAutheliaCtx(t)  	defer mock.Close() -	clock := mocks.TestingClock{} +	clock := utils.TestingClock{}  	clock.Set(time.Now())  	past := clock.Now().Add(-1 * time.Hour) @@ -736,7 +736,7 @@ func TestShouldDestroySessionWhenInactiveForTooLongUsingDurationNotation(t *test  	mock := mocks.NewMockAutheliaCtx(t)  	defer mock.Close() -	clock := mocks.TestingClock{} +	clock := utils.TestingClock{}  	clock.Set(time.Now())  	mock.Ctx.Configuration.Session.Inactivity = time.Second * 10 @@ -833,7 +833,7 @@ func TestShouldRedirectWhenSessionInactiveForTooLongAndRDParamProvided(t *testin  	mock := mocks.NewMockAutheliaCtx(t)  	defer mock.Close() -	clock := mocks.TestingClock{} +	clock := utils.TestingClock{}  	clock.Set(time.Now())  	mock.Ctx.Configuration.Session.Inactivity = testInactivity @@ -1055,7 +1055,7 @@ func TestShouldNotRefreshUserGroupsFromBackend(t *testing.T) {  	mock.UserProviderMock.EXPECT().GetDetails("john").Times(0) -	clock := mocks.TestingClock{} +	clock := utils.TestingClock{}  	clock.Set(time.Now())  	userSession := mock.Ctx.GetSession() @@ -1115,7 +1115,7 @@ func TestShouldNotRefreshUserGroupsFromBackendWhenDisabled(t *testing.T) {  	mock.UserProviderMock.EXPECT().GetDetails("john").Times(0) -	clock := mocks.TestingClock{} +	clock := utils.TestingClock{}  	clock.Set(time.Now())  	userSession := mock.Ctx.GetSession() @@ -1161,7 +1161,7 @@ func TestShouldDestroySessionWhenUserNotExist(t *testing.T) {  	mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(1) -	clock := mocks.TestingClock{} +	clock := utils.TestingClock{}  	clock.Set(time.Now())  	userSession := mock.Ctx.GetSession() @@ -1222,7 +1222,7 @@ func TestShouldGetRemovedUserGroupsFromBackend(t *testing.T) {  	mock.UserProviderMock.EXPECT().GetDetails("john").Return(user, nil).Times(2) -	clock := mocks.TestingClock{} +	clock := utils.TestingClock{}  	clock.Set(time.Now())  	userSession := mock.Ctx.GetSession() @@ -1431,7 +1431,7 @@ func TestShouldNotRedirectRequestsForBypassACLWhenInactiveForTooLong(t *testing.  	mock := mocks.NewMockAutheliaCtx(t)  	defer mock.Close() -	clock := mocks.TestingClock{} +	clock := utils.TestingClock{}  	clock.Set(time.Now())  	past := clock.Now().Add(-1 * time.Hour) diff --git a/internal/mocks/authelia_ctx.go b/internal/mocks/authelia_ctx.go index 22c0f5003..26a4a876f 100644 --- a/internal/mocks/authelia_ctx.go +++ b/internal/mocks/authelia_ctx.go @@ -19,6 +19,7 @@ import (  	"github.com/authelia/authelia/v4/internal/regulation"  	"github.com/authelia/authelia/v4/internal/session"  	"github.com/authelia/authelia/v4/internal/templates" +	"github.com/authelia/authelia/v4/internal/utils"  )  // MockAutheliaCtx a mock of AutheliaCtx. @@ -36,33 +37,13 @@ type MockAutheliaCtx struct {  	UserSession *session.UserSession -	Clock TestingClock -} - -// TestingClock implementation of clock for tests. -type TestingClock struct { -	now time.Time -} - -// Now return the stored clock. -func (dc *TestingClock) Now() time.Time { -	return dc.now -} - -// After return a channel receiving the time after duration has elapsed. -func (dc *TestingClock) After(d time.Duration) <-chan time.Time { -	return time.After(d) -} - -// Set set the time of the clock. -func (dc *TestingClock) Set(now time.Time) { -	dc.now = now +	Clock utils.TestingClock  }  // NewMockAutheliaCtx create an instance of AutheliaCtx mock.  func NewMockAutheliaCtx(t *testing.T) *MockAutheliaCtx {  	mockAuthelia := new(MockAutheliaCtx) -	mockAuthelia.Clock = TestingClock{} +	mockAuthelia.Clock = utils.TestingClock{}  	datetime, _ := time.Parse("2006-Jan-02", "2013-Feb-03")  	mockAuthelia.Clock.Set(datetime) diff --git a/internal/regulation/regulator_test.go b/internal/regulation/regulator_test.go index 37f371169..6b61be4da 100644 --- a/internal/regulation/regulator_test.go +++ b/internal/regulation/regulator_test.go @@ -13,6 +13,7 @@ import (  	"github.com/authelia/authelia/v4/internal/mocks"  	"github.com/authelia/authelia/v4/internal/model"  	"github.com/authelia/authelia/v4/internal/regulation" +	"github.com/authelia/authelia/v4/internal/utils"  )  type RegulatorSuite struct { @@ -22,7 +23,7 @@ type RegulatorSuite struct {  	ctrl        *gomock.Controller  	storageMock *mocks.MockStorage  	config      schema.RegulationConfiguration -	clock       mocks.TestingClock +	clock       utils.TestingClock  }  func (s *RegulatorSuite) SetupTest() { diff --git a/internal/utils/clock.go b/internal/utils/clock.go index 388275f3d..9b5cc423f 100644 --- a/internal/utils/clock.go +++ b/internal/utils/clock.go @@ -20,3 +20,23 @@ func (RealClock) Now() time.Time {  func (RealClock) After(d time.Duration) <-chan time.Time {  	return time.After(d)  } + +// TestingClock implementation of clock for tests. +type TestingClock struct { +	now time.Time +} + +// Now return the stored clock. +func (dc *TestingClock) Now() time.Time { +	return dc.now +} + +// After return a channel receiving the time after duration has elapsed. +func (dc *TestingClock) After(d time.Duration) <-chan time.Time { +	return time.After(d) +} + +// Set set the time of the clock. +func (dc *TestingClock) Set(now time.Time) { +	dc.now = now +} diff --git a/internal/utils/const.go b/internal/utils/const.go index 1188b5f9c..2e65da318 100644 --- a/internal/utils/const.go +++ b/internal/utils/const.go @@ -8,24 +8,9 @@ import (  )  const ( -	windows         = "windows" -	testStringInput = "abcdefghijkl" -  	// RFC3339Zero is the default value for time.Time.Unix().  	RFC3339Zero = int64(-62135596800) -	// TLS13 is the textual representation of TLS 1.3. -	TLS13 = "1.3" - -	// TLS12 is the textual representation of TLS 1.2. -	TLS12 = "1.2" - -	// TLS11 is the textual representation of TLS 1.1. -	TLS11 = "1.1" - -	// TLS10 is the textual representation of TLS 1.0. -	TLS10 = "1.0" -  	clean   = "clean"  	tagged  = "tagged"  	unknown = "unknown" @@ -84,10 +69,6 @@ const (  	Month = Year / 12  ) -const ( -	errFmtLinuxNotFound = "open %s: no such file or directory" -) -  var (  	standardDurationUnits = []string{"ns", "us", "µs", "μs", "ms", "s", "m", "h"}  	reDurationSeconds     = regexp.MustCompile(`^\d+$`) @@ -111,6 +92,12 @@ const (  )  const ( +	// timeUnixEpochAsMicrosoftNTEpoch represents the unix epoch as a Microsoft NT Epoch. +	// The Microsoft NT Epoch is ticks since Jan 1, 1601 (1 tick is 100ns). +	timeUnixEpochAsMicrosoftNTEpoch uint64 = 116444736000000000 +) + +const (  	// CharSetAlphabeticLower are literally just valid alphabetic lowercase printable ASCII chars.  	CharSetAlphabeticLower = "abcdefghijklmnopqrstuvwxyz" @@ -155,5 +142,7 @@ var htmlEscaper = strings.NewReplacer(  // ErrTimeoutReached error thrown when a timeout is reached.  var ErrTimeoutReached = errors.New("timeout reached") -// ErrTLSVersionNotSupported returned when an unknown TLS version supplied. -var ErrTLSVersionNotSupported = errors.New("supplied tls version isn't supported") +const ( +	windows             = "windows" +	errFmtLinuxNotFound = "open %s: no such file or directory" +) diff --git a/internal/utils/const_test.go b/internal/utils/const_test.go new file mode 100644 index 000000000..879485536 --- /dev/null +++ b/internal/utils/const_test.go @@ -0,0 +1,5 @@ +package utils + +const ( +	testStringInput = "abcdefghijkl" +) diff --git a/internal/utils/time.go b/internal/utils/time.go index 51952fcdc..4844e108e 100644 --- a/internal/utils/time.go +++ b/internal/utils/time.go @@ -67,3 +67,8 @@ func ParseDurationString(input string) (duration time.Duration, err error) {  	return time.ParseDuration(out)  } + +// UnixNanoTimeToMicrosoftNTEpoch converts a unix timestamp in nanosecond format to win32 epoch format. +func UnixNanoTimeToMicrosoftNTEpoch(nano int64) (t uint64) { +	return uint64(nano/100) + timeUnixEpochAsMicrosoftNTEpoch +} diff --git a/internal/utils/time_test.go b/internal/utils/time_test.go index f75871ec8..4cd651c9c 100644 --- a/internal/utils/time_test.go +++ b/internal/utils/time_test.go @@ -122,3 +122,11 @@ func TestShouldTimeIntervalsMakeSense(t *testing.T) {  	assert.Equal(t, Year, Day*365)  	assert.Equal(t, Month, Year/12)  } + +func TestShouldConvertKnownUnixNanoTimeToKnownWin32Epoch(t *testing.T) { +	exampleNanoTime := int64(1626234411 * 1000000000) +	win32Epoch := uint64(132707080110000000) + +	assert.Equal(t, win32Epoch, UnixNanoTimeToMicrosoftNTEpoch(exampleNanoTime)) +	assert.Equal(t, timeUnixEpochAsMicrosoftNTEpoch, UnixNanoTimeToMicrosoftNTEpoch(0)) +}  | 
