From 42670c27d59df8fe8115350bec5dc640dc1e4d66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Tue, 14 Jul 2015 15:46:11 +0200 Subject: [PATCH] feat(ldap): more work on org role sync --- pkg/auth/ldap.go | 49 +++++++++++++++++++++++++++++++++++++++++-- pkg/auth/ldap_test.go | 48 +++++++++++++++++++++++++++++++++++++----- pkg/auth/settings.go | 6 ++++-- 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/pkg/auth/ldap.go b/pkg/auth/ldap.go index f58f84263a7..1277259b5ba 100644 --- a/pkg/auth/ldap.go +++ b/pkg/auth/ldap.go @@ -27,7 +27,7 @@ func init() { SearchFilter: "(cn=%s)", SearchBaseDNs: []string{"dc=grafana,dc=org"}, LdapGroups: []*LdapGroupToOrgRole{ - {GroupDN: "cn=users,dc=grafana,dc=org", OrgRole: "Editor"}, + {GroupDN: "cn=users,dc=grafana,dc=org", OrgRole: m.ROLE_EDITOR}, }, }, } @@ -89,7 +89,9 @@ func (a *ldapAuther) login(query *AuthenticateUserQuery) error { func (a *ldapAuther) getGrafanaUserFor(ldapUser *ldapUserInfo) (*m.User, error) { // validate that the user has access - access := false + // if there are no ldap group mappings access is true + // otherwise a single group must match + access := len(a.server.LdapGroups) == 0 for _, ldapGroup := range a.server.LdapGroups { if ldapUser.isMemberOf(ldapGroup.GroupDN) { access = true @@ -129,6 +131,49 @@ func (a *ldapAuther) createGrafanaUser(ldapUser *ldapUserInfo) (*m.User, error) } func (a *ldapAuther) syncOrgRoles(user *m.User, ldapUser *ldapUserInfo) error { + if len(a.server.LdapGroups) == 0 { + return nil + } + + orgsQuery := m.GetUserOrgListQuery{UserId: user.Id} + if err := bus.Dispatch(&orgsQuery); err != nil { + return err + } + + // remove or update org roles + for _, org := range orgsQuery.Result { + for _, group := range a.server.LdapGroups { + if group.OrgId == org.OrgId && ldapUser.isMemberOf(group.GroupDN) { + if org.Role != group.OrgRole { + // update role + } + } else { + // remove role + } + } + } + + for _, group := range a.server.LdapGroups { + if !ldapUser.isMemberOf(group.GroupDN) { + continue + } + + match := false + for _, org := range orgsQuery.Result { + if group.OrgId == org.OrgId { + match = true + } + } + + if !match { + // add role + cmd := m.AddOrgUserCommand{UserId: user.Id, Role: group.OrgRole, OrgId: group.OrgId} + if err := bus.Dispatch(&cmd); err != nil { + return err + } + } + } + return nil } diff --git a/pkg/auth/ldap_test.go b/pkg/auth/ldap_test.go index 18efbcaf77d..afa5ba88aee 100644 --- a/pkg/auth/ldap_test.go +++ b/pkg/auth/ldap_test.go @@ -13,7 +13,9 @@ 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{}) + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{{}}, + }) _, err := ldapAuther.getGrafanaUserFor(&ldapUserInfo{}) So(err, ShouldEqual, ErrInvalidCredentials) @@ -24,7 +26,7 @@ func TestLdapAuther(t *testing.T) { ldapAutherScenario("Given wildcard group match", func(sc *scenarioContext) { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{ - {GroupDN: "*", OrgRole: "Admin", OrgName: "Main"}, + {GroupDN: "*", OrgRole: "Admin"}, }, }) @@ -38,7 +40,7 @@ func TestLdapAuther(t *testing.T) { ldapAutherScenario("Given exact group match", func(sc *scenarioContext) { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{ - {GroupDN: "cn=users", OrgRole: "Admin", OrgName: "Main"}, + {GroupDN: "cn=users", OrgRole: "Admin"}, }, }) @@ -52,7 +54,7 @@ func TestLdapAuther(t *testing.T) { ldapAutherScenario("Given no existing grafana user", func(sc *scenarioContext) { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{ - {GroupDN: "cn=users", OrgRole: "Admin", OrgName: "Main"}, + {GroupDN: "cn=users", OrgRole: "Admin"}, }, }) @@ -78,6 +80,28 @@ func TestLdapAuther(t *testing.T) { }) }) + + Convey("When syncing ldap groups to grafana org roles", t, func() { + + ldapAutherScenario("given no current user orgs", func(sc *scenarioContext) { + ldapAuther := NewLdapAuthenticator(&LdapServerConf{ + LdapGroups: []*LdapGroupToOrgRole{ + {GroupDN: "cn=users", OrgRole: "Admin"}, + }, + }) + + sc.userOrgsQueryReturns([]*m.UserOrgDTO{}) + err := ldapAuther.syncOrgRoles(&m.User{}, &ldapUserInfo{ + MemberOf: []string{"cn=users"}, + }) + + Convey("Should create new org user", func() { + So(err, ShouldBeNil) + So(sc.addOrgUserCommand, ShouldNotBeNil) + So(sc.addOrgUserCommand.Role, ShouldEqual, m.ROLE_ADMIN) + }) + }) + }) } func ldapAutherScenario(desc string, fn scenarioFunc) { @@ -85,18 +109,25 @@ func ldapAutherScenario(desc string, fn scenarioFunc) { 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 }) + bus.AddHandler("test", func(cmd *m.AddOrgUserCommand) error { + sc.addOrgUserCommand = cmd + return nil + }) + fn(sc) }) } type scenarioContext struct { - createUserCmd *m.CreateUserCommand + createUserCmd *m.CreateUserCommand + addOrgUserCommand *m.AddOrgUserCommand } func (sc *scenarioContext) userQueryReturns(user *m.User) { @@ -110,4 +141,11 @@ func (sc *scenarioContext) userQueryReturns(user *m.User) { }) } +func (sc *scenarioContext) userOrgsQueryReturns(orgs []*m.UserOrgDTO) { + bus.AddHandler("test", func(query *m.GetUserOrgListQuery) error { + query.Result = orgs + return nil + }) +} + type scenarioFunc func(c *scenarioContext) diff --git a/pkg/auth/settings.go b/pkg/auth/settings.go index 2647cac7549..d050fe36775 100644 --- a/pkg/auth/settings.go +++ b/pkg/auth/settings.go @@ -1,9 +1,11 @@ package auth +import m "github.com/grafana/grafana/pkg/models" + type LdapGroupToOrgRole struct { GroupDN string - OrgId int - OrgRole string + OrgId int64 + OrgRole m.RoleType } type LdapServerConf struct {