diff --git a/internal/authentication/ldap_connection_factory.go b/internal/authentication/ldap_connection_factory.go index 9cc8b839d..b7b58a14d 100644 --- a/internal/authentication/ldap_connection_factory.go +++ b/internal/authentication/ldap_connection_factory.go @@ -15,6 +15,7 @@ type LDAPConnection interface { Search(searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) Modify(modifyRequest *ldap.ModifyRequest) error + PasswordModify(pwdModifyRequest *ldap.PasswordModifyRequest) error StartTLS(config *tls.Config) error } @@ -48,6 +49,12 @@ func (lc *LDAPConnectionImpl) Modify(modifyRequest *ldap.ModifyRequest) error { return lc.conn.Modify(modifyRequest) } +// PasswordModify modifies an ldap objects password. +func (lc *LDAPConnectionImpl) PasswordModify(pwdModifyRequest *ldap.PasswordModifyRequest) error { + _, err := lc.conn.PasswordModify(pwdModifyRequest) + return err +} + // StartTLS requests the LDAP server upgrades to TLS encryption. func (lc *LDAPConnectionImpl) StartTLS(config *tls.Config) error { return lc.conn.StartTLS(config) diff --git a/internal/authentication/ldap_connection_factory_mock.go b/internal/authentication/ldap_connection_factory_mock.go index 477c97170..c3f285f90 100644 --- a/internal/authentication/ldap_connection_factory_mock.go +++ b/internal/authentication/ldap_connection_factory_mock.go @@ -88,6 +88,20 @@ func (mr *MockLDAPConnectionMockRecorder) Modify(modifyRequest interface{}) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Modify", reflect.TypeOf((*MockLDAPConnection)(nil).Modify), modifyRequest) } +// PasswordModify mocks base method +func (m *MockLDAPConnection) PasswordModify(pwdModifyRequest *ldap.PasswordModifyRequest) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PasswordModify", pwdModifyRequest) + ret0, _ := ret[0].(error) + return ret0 +} + +// PasswordModify indicates an expected call of PasswordModify +func (mr *MockLDAPConnectionMockRecorder) PasswordModify(pwdModifyRequest interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PasswordModify", reflect.TypeOf((*MockLDAPConnection)(nil).Modify), pwdModifyRequest) +} + // StartTLS mocks base method func (m *MockLDAPConnection) StartTLS(config *tls.Config) error { m.ctrl.T.Helper() diff --git a/internal/authentication/ldap_user_provider.go b/internal/authentication/ldap_user_provider.go index 0fcc6a874..576aa787e 100644 --- a/internal/authentication/ldap_user_provider.go +++ b/internal/authentication/ldap_user_provider.go @@ -342,20 +342,30 @@ func (p *LDAPUserProvider) UpdatePassword(inputUsername string, newPassword stri return fmt.Errorf("Unable to update password. Cause: %s", err) } - modifyRequest := ldap.NewModifyRequest(profile.DN, nil) + switch { + case p.supportExtensionPasswdModify: + modifyRequest := ldap.NewPasswordModifyRequest( + profile.DN, + "", + newPassword, + ) - switch p.configuration.Implementation { - case schema.LDAPImplementationActiveDirectory: + err = conn.PasswordModify(modifyRequest) + case p.configuration.Implementation == schema.LDAPImplementationActiveDirectory: + modifyRequest := ldap.NewModifyRequest(profile.DN, nil) utf16 := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) // The password needs to be enclosed in quotes // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/6e803168-f140-4d23-b2d3-c3a8ab5917d2 pwdEncoded, _ := utf16.NewEncoder().String(fmt.Sprintf("\"%s\"", newPassword)) modifyRequest.Replace("unicodePwd", []string{pwdEncoded}) - default: - modifyRequest.Replace("userPassword", []string{newPassword}) - } - err = conn.Modify(modifyRequest) + err = conn.Modify(modifyRequest) + default: + modifyRequest := ldap.NewModifyRequest(profile.DN, nil) + modifyRequest.Replace("userPassword", []string{newPassword}) + + err = conn.Modify(modifyRequest) + } if err != nil { return fmt.Errorf("Unable to update password. Cause: %s", err) diff --git a/internal/authentication/ldap_user_provider_test.go b/internal/authentication/ldap_user_provider_test.go index 8602bd8b1..82d6b1a84 100644 --- a/internal/authentication/ldap_user_provider_test.go +++ b/internal/authentication/ldap_user_provider_test.go @@ -649,10 +649,36 @@ func TestShouldUpdateUserPassword(t *testing.T) { nil, mockFactory) - modifyRequest := ldap.NewModifyRequest("uid=test,dc=example,dc=com", nil) - modifyRequest.Replace("userPassword", []string{"password"}) + pwdModifyRequest := ldap.NewPasswordModifyRequest( + "uid=test,dc=example,dc=com", + "", + "password", + ) gomock.InOrder( + mockFactory.EXPECT(). + DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). + Return(mockConn, nil), + mockConn.EXPECT(). + Bind(gomock.Eq("cn=admin,dc=example,dc=com"), gomock.Eq("password")). + Return(nil), + + mockConn.EXPECT(). + Search(NewExtendedSearchRequestMatcher("(objectClass=*)", "", ldap.ScopeBaseObject, ldap.NeverDerefAliases, false, []string{ldapSupportedExtensionAttribute})). + Return(&ldap.SearchResult{ + Entries: []*ldap.Entry{ + { + DN: "", + Attributes: []*ldap.EntryAttribute{ + { + Name: ldapSupportedExtensionAttribute, + Values: []string{ldapOIDPasswdModifyExtension}, + }, + }, + }, + }, + }, nil), + mockFactory.EXPECT(). DialURL(gomock.Eq("ldap://127.0.0.1:389"), gomock.Any()). Return(mockConn, nil), @@ -683,14 +709,16 @@ func TestShouldUpdateUserPassword(t *testing.T) { }, }, nil), mockConn.EXPECT(). - Modify(modifyRequest). + PasswordModify(pwdModifyRequest). Return(nil), mockConn.EXPECT(). Close(), ) - err := ldapClient.UpdatePassword("john", "password") + err := ldapClient.checkServer() + require.NoError(t, err) + err = ldapClient.UpdatePassword("john", "password") require.NoError(t, err) }