grafana/pkg/services/multildap/multildap_test.go
gotjosh 279249ef56
Multi-LDAP: Do not fail-fast on invalid credentials (#19261)
* Multi-LDAP: Do not fail-fast on invalid credentials

When configuring LDAP authentication, it is very common to have multiple
servers configured. When using user bind (authenticating with LDAP using
the same credentials as the user authenticating to Grafana) we don't
expect all the users to be on all LDAP servers.

Because of this use-case, we should not fail-fast when authenticating on
multiple LDAP server configurations. Instead, we should continue to try
the credentials with the next LDAP server configured.

Fixes #19066
2019-09-23 13:34:05 +01:00

398 lines
8.5 KiB
Go

package multildap
import (
"errors"
"testing"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
. "github.com/smartystreets/goconvey/convey"
)
func TestMultiLDAP(t *testing.T) {
Convey("Multildap", t, func() {
Convey("Ping()", func() {
Convey("Should return error for absent config list", func() {
setup()
multi := New([]*ldap.ServerConfig{})
_, err := multi.Ping()
So(err, ShouldBeError)
So(err, ShouldEqual, ErrNoLDAPServers)
teardown()
})
Convey("Should return an unavailable status on dial error", func() {
mock := setup()
expectedErr := errors.New("Dial error")
mock.dialErrReturn = expectedErr
multi := New([]*ldap.ServerConfig{
{Host: "10.0.0.1", Port: 361},
})
statuses, err := multi.Ping()
So(err, ShouldBeNil)
So(statuses[0].Host, ShouldEqual, "10.0.0.1")
So(statuses[0].Port, ShouldEqual, 361)
So(statuses[0].Available, ShouldBeFalse)
So(statuses[0].Error, ShouldEqual, expectedErr)
So(mock.closeCalledTimes, ShouldEqual, 0)
teardown()
})
Convey("Should get the LDAP server statuses", func() {
mock := setup()
multi := New([]*ldap.ServerConfig{
{Host: "10.0.0.1", Port: 361},
})
statuses, err := multi.Ping()
So(err, ShouldBeNil)
So(statuses[0].Host, ShouldEqual, "10.0.0.1")
So(statuses[0].Port, ShouldEqual, 361)
So(statuses[0].Available, ShouldBeTrue)
So(statuses[0].Error, ShouldBeNil)
So(mock.closeCalledTimes, ShouldEqual, 1)
teardown()
})
})
Convey("Login()", func() {
Convey("Should return error for absent config list", func() {
setup()
multi := New([]*ldap.ServerConfig{})
_, err := multi.Login(&models.LoginUserQuery{})
So(err, ShouldBeError)
So(err, ShouldEqual, ErrNoLDAPServers)
teardown()
})
Convey("Should return a dial error", func() {
mock := setup()
expected := errors.New("Dial error")
mock.dialErrReturn = expected
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, err := multi.Login(&models.LoginUserQuery{})
So(err, ShouldBeError)
So(err, ShouldEqual, expected)
teardown()
})
Convey("Should call underlying LDAP methods", func() {
mock := setup()
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, err := multi.Login(&models.LoginUserQuery{})
So(mock.dialCalledTimes, ShouldEqual, 2)
So(mock.loginCalledTimes, ShouldEqual, 2)
So(mock.closeCalledTimes, ShouldEqual, 2)
So(err, ShouldEqual, ErrInvalidCredentials)
teardown()
})
Convey("Should get login result", func() {
mock := setup()
mock.loginReturn = &models.ExternalUserInfo{
Login: "killa",
}
multi := New([]*ldap.ServerConfig{
{}, {},
})
result, err := multi.Login(&models.LoginUserQuery{})
So(mock.dialCalledTimes, ShouldEqual, 1)
So(mock.loginCalledTimes, ShouldEqual, 1)
So(mock.closeCalledTimes, ShouldEqual, 1)
So(result.Login, ShouldEqual, "killa")
So(err, ShouldBeNil)
teardown()
})
Convey("Should still call a second error for invalid not found error", func() {
mock := setup()
mock.loginErrReturn = ErrCouldNotFindUser
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, err := multi.Login(&models.LoginUserQuery{})
So(mock.dialCalledTimes, ShouldEqual, 2)
So(mock.loginCalledTimes, ShouldEqual, 2)
So(mock.closeCalledTimes, ShouldEqual, 2)
So(err, ShouldEqual, ErrInvalidCredentials)
teardown()
})
Convey("Should still try to auth with the second server after receiving an invalid credentials error from the first", func() {
mock := setup()
mock.loginErrReturn = ErrInvalidCredentials
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, err := multi.Login(&models.LoginUserQuery{})
So(mock.dialCalledTimes, ShouldEqual, 2)
So(mock.loginCalledTimes, ShouldEqual, 2)
So(mock.closeCalledTimes, ShouldEqual, 2)
So(err, ShouldEqual, ErrInvalidCredentials)
teardown()
})
Convey("Should return unknown error", func() {
mock := setup()
expected := errors.New("Something unknown")
mock.loginErrReturn = expected
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, err := multi.Login(&models.LoginUserQuery{})
So(mock.dialCalledTimes, ShouldEqual, 1)
So(mock.loginCalledTimes, ShouldEqual, 1)
So(mock.closeCalledTimes, ShouldEqual, 1)
So(err, ShouldEqual, expected)
teardown()
})
})
Convey("User()", func() {
Convey("Should return error for absent config list", func() {
setup()
multi := New([]*ldap.ServerConfig{})
_, _, err := multi.User("test")
So(err, ShouldBeError)
So(err, ShouldEqual, ErrNoLDAPServers)
teardown()
})
Convey("Should return a dial error", func() {
mock := setup()
expected := errors.New("Dial error")
mock.dialErrReturn = expected
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, _, err := multi.User("test")
So(err, ShouldBeError)
So(err, ShouldEqual, expected)
teardown()
})
Convey("Should call underlying LDAP methods", func() {
mock := setup()
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, _, err := multi.User("test")
So(mock.dialCalledTimes, ShouldEqual, 2)
So(mock.usersCalledTimes, ShouldEqual, 2)
So(mock.closeCalledTimes, ShouldEqual, 2)
So(err, ShouldEqual, ErrDidNotFindUser)
teardown()
})
Convey("Should return some error", func() {
mock := setup()
expected := errors.New("Killa Gorilla")
mock.usersErrReturn = expected
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, _, err := multi.User("test")
So(mock.dialCalledTimes, ShouldEqual, 1)
So(mock.usersCalledTimes, ShouldEqual, 1)
So(mock.closeCalledTimes, ShouldEqual, 1)
So(err, ShouldEqual, expected)
teardown()
})
Convey("Should get only one user", func() {
mock := setup()
mock.usersFirstReturn = []*models.ExternalUserInfo{
{
Login: "one",
},
{
Login: "two",
},
}
multi := New([]*ldap.ServerConfig{
{}, {},
})
user, _, err := multi.User("test")
So(mock.dialCalledTimes, ShouldEqual, 1)
So(mock.usersCalledTimes, ShouldEqual, 1)
So(mock.closeCalledTimes, ShouldEqual, 1)
So(err, ShouldBeNil)
So(user.Login, ShouldEqual, "one")
teardown()
})
})
Convey("Users()", func() {
Convey("Should return error for absent config list", func() {
setup()
multi := New([]*ldap.ServerConfig{})
_, err := multi.Users([]string{"test"})
So(err, ShouldBeError)
So(err, ShouldEqual, ErrNoLDAPServers)
teardown()
})
Convey("Should return a dial error", func() {
mock := setup()
expected := errors.New("Dial error")
mock.dialErrReturn = expected
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, err := multi.Users([]string{"test"})
So(err, ShouldBeError)
So(err, ShouldEqual, expected)
teardown()
})
Convey("Should call underlying LDAP methods", func() {
mock := setup()
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, err := multi.Users([]string{"test"})
So(mock.dialCalledTimes, ShouldEqual, 2)
So(mock.usersCalledTimes, ShouldEqual, 2)
So(mock.closeCalledTimes, ShouldEqual, 2)
So(err, ShouldBeNil)
teardown()
})
Convey("Should return some error", func() {
mock := setup()
expected := errors.New("Killa Gorilla")
mock.usersErrReturn = expected
multi := New([]*ldap.ServerConfig{
{}, {},
})
_, err := multi.Users([]string{"test"})
So(mock.dialCalledTimes, ShouldEqual, 1)
So(mock.usersCalledTimes, ShouldEqual, 1)
So(mock.closeCalledTimes, ShouldEqual, 1)
So(err, ShouldEqual, expected)
teardown()
})
Convey("Should get users", func() {
mock := setup()
mock.usersFirstReturn = []*models.ExternalUserInfo{
{
Login: "one",
},
{
Login: "two",
},
}
mock.usersRestReturn = []*models.ExternalUserInfo{
{
Login: "three",
},
}
multi := New([]*ldap.ServerConfig{
{}, {},
})
users, err := multi.Users([]string{"test"})
So(mock.dialCalledTimes, ShouldEqual, 2)
So(mock.usersCalledTimes, ShouldEqual, 2)
So(mock.closeCalledTimes, ShouldEqual, 2)
So(err, ShouldBeNil)
So(users[0].Login, ShouldEqual, "one")
So(users[1].Login, ShouldEqual, "two")
So(users[2].Login, ShouldEqual, "three")
teardown()
})
})
})
}