From a7b1df34c5e7beb77d8fb913f227ba394882152a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 14 Jul 2015 10:20:21 +0200 Subject: [PATCH] feat(ldap): more unit tests for ldap to grafana user sync --- pkg/auth/auth.go | 2 +- pkg/auth/ldap.go | 60 ++++------ pkg/auth/ldap_test.go | 113 ++++++++++++++++++ pkg/auth/ldap_user.go | 22 ++++ .../setting_ldap.go => auth/settings.go} | 11 +- pkg/setting/setting.go | 1 - 6 files changed, 167 insertions(+), 42 deletions(-) create mode 100644 pkg/auth/ldap_test.go create mode 100644 pkg/auth/ldap_user.go rename pkg/{setting/setting_ldap.go => auth/settings.go} (74%) diff --git a/pkg/auth/auth.go b/pkg/auth/auth.go index 91578370735..31447bfa554 100644 --- a/pkg/auth/auth.go +++ b/pkg/auth/auth.go @@ -30,7 +30,7 @@ func AuthenticateUser(query *AuthenticateUserQuery) error { } if setting.LdapEnabled { - for _, server := range setting.LdapServers { + for _, server := range ldapServers { auther := NewLdapAuthenticator(server) err = auther.login(query) if err == nil || err != ErrInvalidCredentials { diff --git a/pkg/auth/ldap.go b/pkg/auth/ldap.go index b4c17f585fb..4d7637d6e04 100644 --- a/pkg/auth/ldap.go +++ b/pkg/auth/ldap.go @@ -8,12 +8,13 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" ) +var ldapServers []*LdapServerConf + func init() { - setting.LdapServers = []*setting.LdapServerConf{ - &setting.LdapServerConf{ + ldapServers = []*LdapServerConf{ + { UseSSL: false, Host: "127.0.0.1", Port: "389", @@ -25,45 +26,20 @@ func init() { AttrEmail: "email", SearchFilter: "(cn=%s)", SearchBaseDNs: []string{"dc=grafana,dc=org"}, + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=users,dc=grafana,dc=org", OrgName: "Main Org.", OrgRole: "Editor"}, + }, }, } } type ldapAuther struct { - server *setting.LdapServerConf + server *LdapServerConf conn *ldap.Conn } -type ldapUserInfo struct { - FirstName string - LastName string - Username string - Email string - MemberOf []string -} - -func (u *ldapUserInfo) isMemberOfAny(groups []string) bool { - for _, group := range groups { - if u.isMemberOf(group) { - return true - } - } - return false -} - -func (u *ldapUserInfo) isMemberOf(group string) bool { - for _, member := range u.MemberOf { - if member == group { - return true - } - } - return false -} - -func NewLdapAuthenticator(server *setting.LdapServerConf) *ldapAuther { - return &ldapAuther{ - server: server, - } +func NewLdapAuthenticator(server *LdapServerConf) *ldapAuther { + return &ldapAuther{server: server} } func (a *ldapAuther) Dial() error { @@ -108,11 +84,26 @@ func (a *ldapAuther) login(query *AuthenticateUserQuery) error { } func (a *ldapAuther) getGrafanaUserFor(ldapUser *ldapUserInfo) (*m.User, error) { + // validate that the user has access + access := false + for _, ldapGroup := range a.server.LdapGroups { + if ldapUser.isMemberOf(ldapGroup.GroupDN) { + access = true + } + } + + if !access { + log.Info("Ldap Auth: user %s does not belong in any of the specified ldap groups", ldapUser.Username) + return nil, ErrInvalidCredentials + } + // get user from grafana db userQuery := m.GetUserByLoginQuery{LoginOrEmail: ldapUser.Username} if err := bus.Dispatch(&userQuery); err != nil { if err == m.ErrUserNotFound { return a.createGrafanaUser(ldapUser) + } else { + return nil, err } } @@ -221,5 +212,4 @@ func getLdapAttrArray(name string, result *ldap.SearchResult) []string { func createUserFromLdapInfo() error { return nil - } diff --git a/pkg/auth/ldap_test.go b/pkg/auth/ldap_test.go new file mode 100644 index 00000000000..18efbcaf77d --- /dev/null +++ b/pkg/auth/ldap_test.go @@ -0,0 +1,113 @@ +package auth + +import ( + "testing" + + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + . "github.com/smartystreets/goconvey/convey" +) + +func TestLdapAuther(t *testing.T) { + + Convey("When translating ldap user to grafana user", t, func() { + + Convey("Given no ldap group map match", func() { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{}) + _, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{}) + + So(err, ShouldEqual, ErrInvalidCredentials) + }) + + var user1 = &m.User{} + + ldapAutherScenario("Given wildcard group match", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "*", OrgRole: "Admin", OrgName: "Main"}, + }, + }) + + sc.userQueryReturns(user1) + + result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{}) + So(err, ShouldBeNil) + So(result, ShouldEqual, user1) + }) + + ldapAutherScenario("Given exact group match", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=users", OrgRole: "Admin", OrgName: "Main"}, + }, + }) + + sc.userQueryReturns(user1) + + result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{MemberOf: []string{"cn=users"}}) + So(err, ShouldBeNil) + So(result, ShouldEqual, user1) + }) + + ldapAutherScenario("Given no existing grafana user", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=users", OrgRole: "Admin", OrgName: "Main"}, + }, + }) + + sc.userQueryReturns(nil) + + result, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{ + Username: "torkelo", + Email: "my@email.com", + MemberOf: []string{"cn=users"}, + }) + + So(err, ShouldBeNil) + + Convey("Should create new user", func() { + So(sc.createUserCmd.Login, ShouldEqual, "torkelo") + So(sc.createUserCmd.Email, ShouldEqual, "my@email.com") + }) + + Convey("Should return new user", func() { + So(result.Login, ShouldEqual, "torkelo") + }) + + }) + + }) +} + +func ldapAutherScenario(desc string, fn scenarioFunc) { + Convey(desc, func() { + defer bus.ClearBusHandlers() + + sc := &scenarioContext{} + bus.AddHandler("test", func(cmd *m.CreateUserCommand) error { + sc.createUserCmd = cmd + sc.createUserCmd.Result = m.User{Login: cmd.Login} + return nil + }) + + fn(sc) + }) +} + +type scenarioContext struct { + createUserCmd *m.CreateUserCommand +} + +func (sc *scenarioContext) userQueryReturns(user *m.User) { + bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error { + if user == nil { + return m.ErrUserNotFound + } else { + query.Result = user + return nil + } + }) +} + +type scenarioFunc func(c *scenarioContext) diff --git a/pkg/auth/ldap_user.go b/pkg/auth/ldap_user.go new file mode 100644 index 00000000000..d8a885b9ac5 --- /dev/null +++ b/pkg/auth/ldap_user.go @@ -0,0 +1,22 @@ +package auth + +type ldapUserInfo struct { + FirstName string + LastName string + Username string + Email string + MemberOf []string +} + +func (u *ldapUserInfo) isMemberOf(group string) bool { + if group == "*" { + return true + } + + for _, member := range u.MemberOf { + if member == group { + return true + } + } + return false +} diff --git a/pkg/setting/setting_ldap.go b/pkg/auth/settings.go similarity index 74% rename from pkg/setting/setting_ldap.go rename to pkg/auth/settings.go index 1a3c195c085..60f1d4b3db1 100644 --- a/pkg/setting/setting_ldap.go +++ b/pkg/auth/settings.go @@ -1,9 +1,10 @@ -package setting +package auth type LdapGroupToOrgRole struct { - LdapGroupPath string - OrgId int - OrgRole string + GroupDN string + OrgId int + OrgName string + OrgRole string } type LdapServerConf struct { @@ -21,5 +22,5 @@ type LdapServerConf struct { SearchFilter string SearchBaseDNs []string - LdapGroups []LdapGroupToOrgRole + LdapGroups []*LdapGroupToOrgRole } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index c10bf729129..81a595a0cf1 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -119,7 +119,6 @@ var ( // LDAP LdapEnabled bool - LdapServers []*LdapServerConf // SMTP email settings Smtp SmtpSettings