More unit tests for LDAP DNs which contain special chars

Adding explicit coverage for PerformRefresh().
This commit is contained in:
Ryan Richard 2022-05-03 15:43:01 -07:00
parent d6e61012c6
commit acc6c50e48
1 changed files with 216 additions and 182 deletions

View File

@ -51,6 +51,8 @@ const (
testUserSearchResultUIDAttributeValue = "some-upstream-uid-value" testUserSearchResultUIDAttributeValue = "some-upstream-uid-value"
testGroupSearchResultGroupNameAttributeValue1 = "some-upstream-group-name-value1" testGroupSearchResultGroupNameAttributeValue1 = "some-upstream-group-name-value1"
testGroupSearchResultGroupNameAttributeValue2 = "some-upstream-group-name-value2" testGroupSearchResultGroupNameAttributeValue2 = "some-upstream-group-name-value2"
testUserDNWithSpecialChars = `user DN with * \ special characters ()`
testUserDNWithSpecialCharsEscaped = `user DN with \2a \5c special characters \28\29`
expectedGroupSearchPageSize = uint32(250) expectedGroupSearchPageSize = uint32(250)
) )
@ -529,7 +531,7 @@ func TestEndUserAuthentication(t *testing.T) {
Return(&ldap.SearchResult{ Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: `result DN with * \ special characters ()`, DN: testUserDNWithSpecialChars,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}), ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}),
@ -538,16 +540,16 @@ func TestEndUserAuthentication(t *testing.T) {
}, },
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) { conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
escapedDN := `result DN with \2a \5c special characters \28\29` escapedDN := testUserDNWithSpecialCharsEscaped
r.Filter = fmt.Sprintf("(some-group-filter=%s-and-more-filter=%s)", escapedDN, escapedDN) r.Filter = fmt.Sprintf("(some-group-filter=%s-and-more-filter=%s)", escapedDN, escapedDN)
}), expectedGroupSearchPageSize).Return(exampleGroupSearchResult, nil).Times(1) }), expectedGroupSearchPageSize).Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(`result DN with * \ special characters ()`, testUpstreamPassword).Times(1) conn.EXPECT().Bind(testUserDNWithSpecialChars, testUpstreamPassword).Times(1)
}, },
wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) { wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) {
r.DN = `result DN with * \ special characters ()` r.DN = testUserDNWithSpecialChars
}), }),
}, },
{ {
@ -563,7 +565,7 @@ func TestEndUserAuthentication(t *testing.T) {
Return(&ldap.SearchResult{ Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: `result DN with * \ special characters ()`, DN: testUserDNWithSpecialChars,
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}), ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testUserSearchResultUsernameAttributeValue}),
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}), ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testUserSearchResultUIDAttributeValue}),
@ -572,15 +574,15 @@ func TestEndUserAuthentication(t *testing.T) {
}, },
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) { conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
r.Filter = fmt.Sprintf("(member=%s)", `result DN with \2a \5c special characters \28\29`) r.Filter = fmt.Sprintf("(member=%s)", testUserDNWithSpecialCharsEscaped)
}), expectedGroupSearchPageSize).Return(exampleGroupSearchResult, nil).Times(1) }), expectedGroupSearchPageSize).Return(exampleGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
bindEndUserMocks: func(conn *mockldapconn.MockConn) { bindEndUserMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(`result DN with * \ special characters ()`, testUpstreamPassword).Times(1) conn.EXPECT().Bind(testUserDNWithSpecialChars, testUpstreamPassword).Times(1)
}, },
wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) { wantAuthResponse: expectedAuthResponse(func(r *authenticators.Response) {
r.DN = `result DN with * \ special characters ()` r.DN = testUserDNWithSpecialChars
}), }),
}, },
{ {
@ -1219,7 +1221,9 @@ func TestEndUserAuthentication(t *testing.T) {
func TestUpstreamRefresh(t *testing.T) { func TestUpstreamRefresh(t *testing.T) {
pwdLastSetAttribute := "pwdLastSet" pwdLastSetAttribute := "pwdLastSet"
expectedUserSearch := &ldap.SearchRequest{
expectedUserSearch := func(editFunc func(r *ldap.SearchRequest)) *ldap.SearchRequest {
request := &ldap.SearchRequest{
BaseDN: testUserSearchResultDNValue, BaseDN: testUserSearchResultDNValue,
Scope: ldap.ScopeBaseObject, Scope: ldap.ScopeBaseObject,
DerefAliases: ldap.NeverDerefAliases, DerefAliases: ldap.NeverDerefAliases,
@ -1230,8 +1234,14 @@ func TestUpstreamRefresh(t *testing.T) {
Attributes: []string{testUserSearchUsernameAttribute, testUserSearchUIDAttribute, pwdLastSetAttribute}, Attributes: []string{testUserSearchUsernameAttribute, testUserSearchUIDAttribute, pwdLastSetAttribute},
Controls: nil, // don't need paging because we set the SizeLimit so small Controls: nil, // don't need paging because we set the SizeLimit so small
} }
if editFunc != nil {
editFunc(request)
}
return request
}
expectedGroupSearch := &ldap.SearchRequest{ expectedGroupSearch := func(editFunc func(r *ldap.SearchRequest)) *ldap.SearchRequest {
request := &ldap.SearchRequest{
BaseDN: testGroupSearchBase, BaseDN: testGroupSearchBase,
Scope: ldap.ScopeWholeSubtree, Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases, DerefAliases: ldap.NeverDerefAliases,
@ -1242,6 +1252,11 @@ func TestUpstreamRefresh(t *testing.T) {
Attributes: []string{testGroupSearchGroupNameAttribute}, Attributes: []string{testGroupSearchGroupNameAttribute},
Controls: nil, // nil because ldap.SearchWithPaging() will set the appropriate controls for us Controls: nil, // nil because ldap.SearchWithPaging() will set the appropriate controls for us
} }
if editFunc != nil {
editFunc(request)
}
return request
}
happyPathUserSearchResult := &ldap.SearchResult{ happyPathUserSearchResult := &ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
@ -1266,68 +1281,7 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
} }
providerConfig := &ProviderConfig{ happyPathGroupSearchResult := &ldap.SearchResult{
Name: "some-provider-name",
Host: testHost,
CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test
ConnectionProtocol: TLS,
BindUsername: testBindUsername,
BindPassword: testBindPassword,
UserSearch: UserSearchConfig{
Base: testUserSearchBase,
UIDAttribute: testUserSearchUIDAttribute,
UsernameAttribute: testUserSearchUsernameAttribute,
},
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute),
},
}
tests := []struct {
name string
providerConfig *ProviderConfig
setupMocks func(conn *mockldapconn.MockConn)
dialError error
wantErr string
wantGroups []string
}{
{
name: "happy path where searching the dn returns a single entry",
providerConfig: providerConfig,
setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantGroups: []string{},
},
{
name: "happy path where group search returns groups",
providerConfig: &ProviderConfig{
Name: "some-provider-name",
Host: testHost,
CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test
ConnectionProtocol: TLS,
BindUsername: testBindUsername,
BindPassword: testBindPassword,
UserSearch: UserSearchConfig{
Base: testUserSearchBase,
UIDAttribute: testUserSearchUIDAttribute,
UsernameAttribute: testUserSearchUsernameAttribute,
},
GroupSearch: GroupSearchConfig{
Base: testGroupSearchBase,
Filter: testGroupSearchFilter,
GroupNameAttribute: testGroupSearchGroupNameAttribute,
},
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute),
},
},
setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch, expectedGroupSearchPageSize).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testGroupSearchResultDNValue1, DN: testGroupSearchResultDNValue1,
@ -1344,14 +1298,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
Referrals: []string{}, // note that we are not following referrals at this time Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{}, Controls: []ldap.Control{},
}, nil).Times(1) }
conn.EXPECT().Close().Times(1)
}, providerConfig := func(editFunc func(p *ProviderConfig)) *ProviderConfig {
wantGroups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2}, config := &ProviderConfig{
},
{
name: "happy path where group search returns no groups",
providerConfig: &ProviderConfig{
Name: "some-provider-name", Name: "some-provider-name",
Host: testHost, Host: testHost,
CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test
@ -1371,11 +1321,130 @@ func TestUpstreamRefresh(t *testing.T) {
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{ RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute), pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute),
}, },
}, }
if editFunc != nil {
editFunc(config)
}
return config
}
tests := []struct {
name string
providerConfig *ProviderConfig
setupMocks func(conn *mockldapconn.MockConn)
refreshUserDN string
dialError error
wantErr string
wantGroups []string
}{
{
name: "happy path without group search where searching the dn returns a single entry",
providerConfig: providerConfig(func(p *ProviderConfig) {
p.GroupSearch = GroupSearchConfig{}
}),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1) conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch, expectedGroupSearchPageSize).Return(&ldap.SearchResult{ conn.EXPECT().Close().Times(1)
},
wantGroups: []string{},
},
{
name: "happy path where group search returns groups",
providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).Return(happyPathGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
wantGroups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
},
{
name: "happy path when the user DN has special LDAP search filter characters then they must be properly escaped in the custom group search filter",
providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) {
r.BaseDN = testUserDNWithSpecialChars
})).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testUserDNWithSpecialChars,
Attributes: []*ldap.EntryAttribute{
{
Name: testUserSearchUsernameAttribute,
Values: []string{testUserSearchResultUsernameAttributeValue},
},
{
Name: testUserSearchUIDAttribute,
ByteValues: [][]byte{[]byte(testUserSearchResultUIDAttributeValue)},
},
{
Name: pwdLastSetAttribute,
Values: []string{"132801740800000000"},
ByteValues: [][]byte{[]byte("132801740800000000")},
},
},
},
},
}, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
r.Filter = fmt.Sprintf("(some-group-filter=%s-and-more-filter=%s)", testUserDNWithSpecialCharsEscaped, testUserDNWithSpecialCharsEscaped)
}), expectedGroupSearchPageSize).Return(happyPathGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
refreshUserDN: testUserDNWithSpecialChars,
wantGroups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
},
{
name: "when the user DN has special LDAP search filter characters then they must be properly escaped in the default group search filter",
providerConfig: providerConfig(func(p *ProviderConfig) {
p.GroupSearch.Filter = ""
}),
setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(func(r *ldap.SearchRequest) {
r.BaseDN = testUserDNWithSpecialChars
})).
Return(&ldap.SearchResult{
Entries: []*ldap.Entry{
{
DN: testUserDNWithSpecialChars,
Attributes: []*ldap.EntryAttribute{
{
Name: testUserSearchUsernameAttribute,
Values: []string{testUserSearchResultUsernameAttributeValue},
},
{
Name: testUserSearchUIDAttribute,
ByteValues: [][]byte{[]byte(testUserSearchResultUIDAttributeValue)},
},
{
Name: pwdLastSetAttribute,
Values: []string{"132801740800000000"},
ByteValues: [][]byte{[]byte("132801740800000000")},
},
},
},
},
}, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(func(r *ldap.SearchRequest) {
r.Filter = fmt.Sprintf("(member=%s)", testUserDNWithSpecialCharsEscaped)
}), expectedGroupSearchPageSize).Return(happyPathGroupSearchResult, nil).Times(1)
conn.EXPECT().Close().Times(1)
},
refreshUserDN: testUserDNWithSpecialChars,
wantGroups: []string{testGroupSearchResultGroupNameAttributeValue1, testGroupSearchResultGroupNameAttributeValue2},
},
{
name: "happy path where group search returns no groups",
providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{}, Entries: []*ldap.Entry{},
Referrals: []string{}, // note that we are not following referrals at this time Referrals: []string{}, // note that we are not following referrals at this time
Controls: []ldap.Control{}, Controls: []ldap.Control{},
@ -1386,44 +1455,25 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "happy path where group search is configured but skipGroupRefresh is set", name: "happy path where group search is configured but skipGroupRefresh is set",
providerConfig: &ProviderConfig{ providerConfig: providerConfig(func(p *ProviderConfig) {
Name: "some-provider-name", p.GroupSearch.SkipGroupRefresh = true
Host: testHost, }),
CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test
ConnectionProtocol: TLS,
BindUsername: testBindUsername,
BindPassword: testBindPassword,
UserSearch: UserSearchConfig{
Base: testUserSearchBase,
UIDAttribute: testUserSearchUIDAttribute,
UsernameAttribute: testUserSearchUsernameAttribute,
},
GroupSearch: GroupSearchConfig{
Base: testGroupSearchBase,
Filter: testGroupSearchFilter,
GroupNameAttribute: testGroupSearchGroupNameAttribute,
SkipGroupRefresh: true,
},
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute),
},
},
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1) // note that group search is not expected conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1) // note that group search is not expected
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
wantGroups: nil, // do not update groups wantGroups: nil, // do not update groups
}, },
{ {
name: "error where dial fails", name: "error where dial fails",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
dialError: errors.New("some dial error"), dialError: errors.New("some dial error"),
wantErr: "error dialing host \"ldap.example.com:8443\": some dial error", wantErr: "error dialing host \"ldap.example.com:8443\": some dial error",
}, },
{ {
name: "error binding", name: "error binding",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Return(errors.New("some bind error")).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Return(errors.New("some bind error")).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
@ -1432,10 +1482,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result returns no entries", name: "search result returns no entries",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{}, Entries: []*ldap.Entry{},
}, nil).Times(1) }, nil).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
@ -1444,20 +1494,20 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "error searching", name: "error searching",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(nil, errors.New("some search error")) conn.EXPECT().Search(expectedUserSearch(nil)).Return(nil, errors.New("some search error"))
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
wantErr: "error searching for user \"some-upstream-user-dn\": some search error", wantErr: "error searching for user \"some-upstream-user-dn\": some search error",
}, },
{ {
name: "search result returns more than one entry", name: "search result returns more than one entry",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testUserSearchResultDNValue, DN: testUserSearchResultDNValue,
@ -1475,10 +1525,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result has wrong uid", name: "search result has wrong uid",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testUserSearchResultDNValue, DN: testUserSearchResultDNValue,
@ -1501,10 +1551,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result has wrong username", name: "search result has wrong username",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testUserSearchResultDNValue, DN: testUserSearchResultDNValue,
@ -1523,10 +1573,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result has no dn", name: "search result has no dn",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
Attributes: []*ldap.EntryAttribute{ Attributes: []*ldap.EntryAttribute{
@ -1548,10 +1598,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result has 0 values for username attribute", name: "search result has 0 values for username attribute",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testUserSearchResultDNValue, DN: testUserSearchResultDNValue,
@ -1574,10 +1624,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result has more than one value for username attribute", name: "search result has more than one value for username attribute",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testUserSearchResultDNValue, DN: testUserSearchResultDNValue,
@ -1600,10 +1650,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result has 0 values for uid attribute", name: "search result has 0 values for uid attribute",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testUserSearchResultDNValue, DN: testUserSearchResultDNValue,
@ -1626,10 +1676,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result has 2 values for uid attribute", name: "search result has 2 values for uid attribute",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testUserSearchResultDNValue, DN: testUserSearchResultDNValue,
@ -1652,10 +1702,10 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "search result has a changed pwdLastSet value", name: "search result has a changed pwdLastSet value",
providerConfig: providerConfig, providerConfig: providerConfig(nil),
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(&ldap.SearchResult{ conn.EXPECT().Search(expectedUserSearch(nil)).Return(&ldap.SearchResult{
Entries: []*ldap.Entry{ Entries: []*ldap.Entry{
{ {
DN: testUserSearchResultDNValue, DN: testUserSearchResultDNValue,
@ -1682,31 +1732,11 @@ func TestUpstreamRefresh(t *testing.T) {
}, },
{ {
name: "group search returns an error", name: "group search returns an error",
providerConfig: &ProviderConfig{ providerConfig: providerConfig(nil),
Name: "some-provider-name",
Host: testHost,
CABundle: nil, // this field is only used by the production dialer, which is replaced by a mock for this test
ConnectionProtocol: TLS,
BindUsername: testBindUsername,
BindPassword: testBindPassword,
UserSearch: UserSearchConfig{
Base: testUserSearchBase,
UIDAttribute: testUserSearchUIDAttribute,
UsernameAttribute: testUserSearchUsernameAttribute,
},
GroupSearch: GroupSearchConfig{
Base: testGroupSearchBase,
Filter: testGroupSearchFilter,
GroupNameAttribute: testGroupSearchGroupNameAttribute,
},
RefreshAttributeChecks: map[string]func(*ldap.Entry, provider.StoredRefreshAttributes) error{
pwdLastSetAttribute: AttributeUnchangedSinceLogin(pwdLastSetAttribute),
},
},
setupMocks: func(conn *mockldapconn.MockConn) { setupMocks: func(conn *mockldapconn.MockConn) {
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1) conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
conn.EXPECT().Search(expectedUserSearch).Return(happyPathUserSearchResult, nil).Times(1) conn.EXPECT().Search(expectedUserSearch(nil)).Return(happyPathUserSearchResult, nil).Times(1)
conn.EXPECT().SearchWithPaging(expectedGroupSearch, expectedGroupSearchPageSize).Return(nil, errors.New("some search error")).Times(1) conn.EXPECT().SearchWithPaging(expectedGroupSearch(nil), expectedGroupSearchPageSize).Return(nil, errors.New("some search error")).Times(1)
conn.EXPECT().Close().Times(1) conn.EXPECT().Close().Times(1)
}, },
wantErr: "error searching for group memberships for user with DN \"some-upstream-user-dn\": some search error", wantErr: "error searching for group memberships for user with DN \"some-upstream-user-dn\": some search error",
@ -1735,13 +1765,17 @@ func TestUpstreamRefresh(t *testing.T) {
return conn, nil return conn, nil
}) })
if tt.refreshUserDN == "" {
tt.refreshUserDN = testUserSearchResultDNValue // default for all tests
}
initialPwdLastSetEncoded := base64.RawURLEncoding.EncodeToString([]byte("132801740800000000")) initialPwdLastSetEncoded := base64.RawURLEncoding.EncodeToString([]byte("132801740800000000"))
ldapProvider := New(*tt.providerConfig) ldapProvider := New(*tt.providerConfig)
subject := "ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU" subject := "ldaps://ldap.example.com:8443?base=some-upstream-user-base-dn&sub=c29tZS11cHN0cmVhbS11aWQtdmFsdWU"
groups, err := ldapProvider.PerformRefresh(context.Background(), provider.StoredRefreshAttributes{ groups, err := ldapProvider.PerformRefresh(context.Background(), provider.StoredRefreshAttributes{
Username: testUserSearchResultUsernameAttributeValue, Username: testUserSearchResultUsernameAttributeValue,
Subject: subject, Subject: subject,
DN: testUserSearchResultDNValue, DN: tt.refreshUserDN,
AdditionalAttributes: map[string]string{pwdLastSetAttribute: initialPwdLastSetEncoded}, AdditionalAttributes: map[string]string{pwdLastSetAttribute: initialPwdLastSetEncoded},
}) })
if tt.wantErr != "" { if tt.wantErr != "" {