Handle error cases during LDAP user search and bind
This commit is contained in:
parent
f0c4305e53
commit
7b8c86b38e
@ -188,7 +188,6 @@ func (p *Provider) AuthenticateUser(ctx context.Context, username, password stri
|
|||||||
|
|
||||||
err = conn.Bind(p.BindUsername, p.BindPassword)
|
err = conn.Bind(p.BindUsername, p.BindPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO test this
|
|
||||||
return nil, false, fmt.Errorf(`error binding as "%s" before user search: %w`, p.BindUsername, err)
|
return nil, false, fmt.Errorf(`error binding as "%s" before user search: %w`, p.BindUsername, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,37 +209,31 @@ func (p *Provider) AuthenticateUser(ctx context.Context, username, password stri
|
|||||||
func (p *Provider) searchAndBindUser(conn Conn, username string, password string) (string, string, error) {
|
func (p *Provider) searchAndBindUser(conn Conn, username string, password string) (string, string, error) {
|
||||||
searchResult, err := conn.Search(p.userSearchRequest(username))
|
searchResult, err := conn.Search(p.userSearchRequest(username))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO test this
|
|
||||||
return "", "", fmt.Errorf(`error searching for user "%s": %w`, username, err)
|
return "", "", fmt.Errorf(`error searching for user "%s": %w`, username, err)
|
||||||
}
|
}
|
||||||
if len(searchResult.Entries) != 1 {
|
if len(searchResult.Entries) != 1 {
|
||||||
// TODO test this
|
|
||||||
return "", "", fmt.Errorf(`searching for user "%s" resulted in %d search results, but expected 1 result`,
|
return "", "", fmt.Errorf(`searching for user "%s" resulted in %d search results, but expected 1 result`,
|
||||||
username, len(searchResult.Entries),
|
username, len(searchResult.Entries),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
userEntry := searchResult.Entries[0]
|
userEntry := searchResult.Entries[0]
|
||||||
if len(userEntry.DN) == 0 {
|
if len(userEntry.DN) == 0 {
|
||||||
// TODO test this
|
|
||||||
return "", "", fmt.Errorf(`searching for user "%s" resulted in search result without DN`, username)
|
return "", "", fmt.Errorf(`searching for user "%s" resulted in search result without DN`, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedUsername, err := p.getSearchResultAttributeValue(p.UserSearch.UsernameAttribute, userEntry, username)
|
mappedUsername, err := p.getSearchResultAttributeValue(p.UserSearch.UsernameAttribute, userEntry, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO test this
|
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedUID, err := p.getSearchResultAttributeValue(p.UserSearch.UIDAttribute, userEntry, username)
|
mappedUID, err := p.getSearchResultAttributeValue(p.UserSearch.UIDAttribute, userEntry, username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO test this
|
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take care that any other LDAP commands after this bind will be run as this user instead of as the configured BindUsername!
|
// Take care that any other LDAP commands after this bind will be run as this user instead of as the configured BindUsername!
|
||||||
err = conn.Bind(userEntry.DN, password)
|
err = conn.Bind(userEntry.DN, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO test this
|
|
||||||
return "", "", fmt.Errorf(`error binding for user "%s" using provided password against DN "%s": %w`, username, userEntry.DN, err)
|
return "", "", fmt.Errorf(`error binding for user "%s" using provided password against DN "%s": %w`, username, userEntry.DN, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -294,7 +287,6 @@ func (p *Provider) getSearchResultAttributeValue(attributeName string, fromUserE
|
|||||||
attributeValues := fromUserEntry.GetAttributeValues(attributeName)
|
attributeValues := fromUserEntry.GetAttributeValues(attributeName)
|
||||||
|
|
||||||
if len(attributeValues) != 1 {
|
if len(attributeValues) != 1 {
|
||||||
// TODO test this
|
|
||||||
return "", fmt.Errorf(`found %d values for attribute "%s" while searching for user "%s", but expected 1 result`,
|
return "", fmt.Errorf(`found %d values for attribute "%s" while searching for user "%s", but expected 1 result`,
|
||||||
len(attributeValues), attributeName, username,
|
len(attributeValues), attributeName, username,
|
||||||
)
|
)
|
||||||
@ -302,7 +294,6 @@ func (p *Provider) getSearchResultAttributeValue(attributeName string, fromUserE
|
|||||||
|
|
||||||
attributeValue := attributeValues[0]
|
attributeValue := attributeValues[0]
|
||||||
if len(attributeValue) == 0 {
|
if len(attributeValue) == 0 {
|
||||||
// TODO test this
|
|
||||||
return "", fmt.Errorf(`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`,
|
return "", fmt.Errorf(`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`,
|
||||||
attributeName, username,
|
attributeName, username,
|
||||||
)
|
)
|
||||||
|
@ -255,6 +255,233 @@ func TestAuthenticateUser(t *testing.T) {
|
|||||||
dialError: errors.New("some dial error"),
|
dialError: errors.New("some dial error"),
|
||||||
wantError: fmt.Sprintf(`error dialing host "%s": some dial error`, testHost),
|
wantError: fmt.Sprintf(`error dialing host "%s": some dial error`, testHost),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "when binding as the bind user returns an error",
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Return(errors.New("some bind error")).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`error binding as "%s" before user search: some bind error`, testBindUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns an error",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(nil, errors.New("some search error")).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`error searching for user "%s": some search error`, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns no results",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`searching for user "%s" resulted in 0 search results, but expected 1 result`, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns multiple results",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{DN: testSearchResultDNValue},
|
||||||
|
{DN: "some-other-dn"},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`searching for user "%s" resulted in 2 search results, but expected 1 result`, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns a user without a DN",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{DN: ""},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`searching for user "%s" resulted in search result without DN`, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns a user without an expected username attribute",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{
|
||||||
|
DN: testSearchResultDNValue,
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUsernameAttribute, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns a user with too many values for the expected username attribute",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{
|
||||||
|
DN: testSearchResultDNValue,
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{
|
||||||
|
testSearchResultUsernameAttributeValue,
|
||||||
|
"unexpected-additional-value",
|
||||||
|
}),
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUsernameAttribute, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns a user with an empty value for the expected username attribute",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{
|
||||||
|
DN: testSearchResultDNValue,
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{""}),
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`, testUserSearchUsernameAttribute, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns a user without an expected UID attribute",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{
|
||||||
|
DN: testSearchResultDNValue,
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`found 0 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUIDAttribute, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns a user with too many values for the expected UID attribute",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{
|
||||||
|
DN: testSearchResultDNValue,
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{
|
||||||
|
testSearchResultUIDAttributeValue,
|
||||||
|
"unexpected-additional-value",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`found 2 values for attribute "%s" while searching for user "%s", but expected 1 result`, testUserSearchUIDAttribute, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when searching for the user returns a user with an empty value for the expected UID attribute",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{
|
||||||
|
DN: testSearchResultDNValue,
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{""}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`found empty value for attribute "%s" while searching for user "%s", but expected value to be non-empty`, testUserSearchUIDAttribute, testUpstreamUsername),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "when binding as the found user returns an error",
|
||||||
|
username: testUpstreamUsername,
|
||||||
|
password: testUpstreamPassword,
|
||||||
|
provider: provider(nil),
|
||||||
|
setupMocks: func(conn *mockldapconn.MockConn) {
|
||||||
|
conn.EXPECT().Bind(testBindUsername, testBindPassword).Times(1)
|
||||||
|
conn.EXPECT().Search(expectedSearch(nil)).Return(&ldap.SearchResult{
|
||||||
|
Entries: []*ldap.Entry{
|
||||||
|
{
|
||||||
|
DN: testSearchResultDNValue,
|
||||||
|
Attributes: []*ldap.EntryAttribute{
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUsernameAttribute, []string{testSearchResultUsernameAttributeValue}),
|
||||||
|
ldap.NewEntryAttribute(testUserSearchUIDAttribute, []string{testSearchResultUIDAttributeValue}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil).Times(1)
|
||||||
|
conn.EXPECT().Bind(testSearchResultDNValue, testUpstreamPassword).Return(errors.New("some bind error")).Times(1)
|
||||||
|
conn.EXPECT().Close().Times(1)
|
||||||
|
},
|
||||||
|
wantError: fmt.Sprintf(`error binding for user "%s" using provided password against DN "%s": some bind error`, testUpstreamUsername, testSearchResultDNValue),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
|
Loading…
Reference in New Issue
Block a user