diff --git a/pkg/services/multildap/multildap.go b/pkg/services/multildap/multildap.go index 6c2baf1671a..3747a1aebd6 100644 --- a/pkg/services/multildap/multildap.go +++ b/pkg/services/multildap/multildap.go @@ -22,6 +22,9 @@ var ErrNoLDAPServers = errors.New("No LDAP servers are configured") // ErrDidNotFindUser if request for user is unsuccessful var ErrDidNotFindUser = errors.New("Did not find a user") +// newLDAP return instance of the single LDAP server +var newLDAP = ldap.New + // IMultiLDAP is interface for MultiLDAP type IMultiLDAP interface { Login(query *models.LoginUserQuery) ( @@ -53,12 +56,13 @@ func New(configs []*ldap.ServerConfig) IMultiLDAP { func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) ( *models.ExternalUserInfo, error, ) { + if len(multiples.configs) == 0 { return nil, ErrNoLDAPServers } for _, config := range multiples.configs { - server := ldap.New(config) + server := newLDAP(config) if err := server.Dial(); err != nil { return nil, err @@ -67,7 +71,6 @@ func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) ( defer server.Close() user, err := server.Login(query) - if user != nil { return user, nil } @@ -80,8 +83,6 @@ func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) ( if err != nil { return nil, err } - - return user, nil } // Return invalid credentials if we couldn't find the user anywhere @@ -100,7 +101,7 @@ func (multiples *MultiLDAP) User(login string) ( search := []string{login} for _, config := range multiples.configs { - server := ldap.New(config) + server := newLDAP(config) if err := server.Dial(); err != nil { return nil, err @@ -133,7 +134,7 @@ func (multiples *MultiLDAP) Users(logins []string) ( } for _, config := range multiples.configs { - server := ldap.New(config) + server := newLDAP(config) if err := server.Dial(); err != nil { return nil, err diff --git a/pkg/services/multildap/multildap_test.go b/pkg/services/multildap/multildap_test.go new file mode 100644 index 00000000000..f1ab230619e --- /dev/null +++ b/pkg/services/multildap/multildap_test.go @@ -0,0 +1,327 @@ +package multildap + +import ( + "errors" + "testing" + + . "github.com/smartystreets/goconvey/convey" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ldap" +) + +func TestMultiLDAP(t *testing.T) { + Convey("Multildap", t, func() { + 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 cred error", 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() + }) + }) + }) +} diff --git a/pkg/services/multildap/test.go b/pkg/services/multildap/test.go new file mode 100644 index 00000000000..183f50d0fea --- /dev/null +++ b/pkg/services/multildap/test.go @@ -0,0 +1,61 @@ +package multildap + +import ( + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/ldap" +) + +type mockLDAP struct { + dialCalledTimes int + loginCalledTimes int + closeCalledTimes int + usersCalledTimes int + + dialErrReturn error + + loginErrReturn error + loginReturn *models.ExternalUserInfo + + usersErrReturn error + usersFirstReturn []*models.ExternalUserInfo + usersRestReturn []*models.ExternalUserInfo +} + +func (mock *mockLDAP) Login(*models.LoginUserQuery) (*models.ExternalUserInfo, error) { + + mock.loginCalledTimes = mock.loginCalledTimes + 1 + return mock.loginReturn, mock.loginErrReturn +} +func (mock *mockLDAP) Users([]string) ([]*models.ExternalUserInfo, error) { + mock.usersCalledTimes = mock.usersCalledTimes + 1 + + if mock.usersCalledTimes == 1 { + return mock.usersFirstReturn, mock.usersErrReturn + } + + return mock.usersRestReturn, mock.usersErrReturn +} +func (mock *mockLDAP) InitialBind(string, string) error { + return nil +} +func (mock *mockLDAP) Dial() error { + mock.dialCalledTimes = mock.dialCalledTimes + 1 + return mock.dialErrReturn +} +func (mock *mockLDAP) Close() { + mock.closeCalledTimes = mock.closeCalledTimes + 1 +} + +func setup() *mockLDAP { + mock := &mockLDAP{} + + newLDAP = func(config *ldap.ServerConfig) ldap.IServer { + return mock + } + + return mock +} + +func teardown() { + newLDAP = ldap.New +}