Commit e8256f0a by Alex Bligh

Add support for POSIX LDAP schema

In the POSIX LDAP schema, there is no 'memberOf' attribute returned
in relation to which groups a person is a member of. Rather, it is
necessary to query the group objects which have the people as members.
This commit adds an additional filter, which if specified explicitly
searches for groups, rather than relying on the 'memberOf' attribute.
This enables Grafana to work with LDAP POSIX schema (e.g. OpenLDAP
etc.)

Signed-off-by: Alex Bligh <alex@alex.org.uk>
parent 458e6da7
...@@ -18,11 +18,32 @@ bind_dn = "cn=admin,dc=grafana,dc=org" ...@@ -18,11 +18,32 @@ bind_dn = "cn=admin,dc=grafana,dc=org"
# Search user bind password # Search user bind password
bind_password = 'grafana' bind_password = 'grafana'
# Schema's supporting memberOf
# Search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" # Search filter, for example "(cn=%s)" or "(sAMAccountName=%s)"
search_filter = "(cn=%s)" search_filter = "(cn=%s)"
# An array of base dns to search through # An array of base dns to search through
search_base_dns = ["dc=grafana,dc=org"] search_base_dns = ["dc=grafana,dc=org"]
# Uncomment this section (and comment out the previous 2 entries) to use POSIX schema.
# In POSIX LDAP schemas, querying the people 'ou' gives you entries that do not have a
# memberOf attribute, so a secondary query must be made for groups. This is done by
# enabling group_search_filter below. You must also set
# member_of = "cn"
# in [servers.attributes] below.
#
# Search filter, used to retrieve the user
# search_filter = "(uid=%s)"
#
# An array of the base DNs to search through for users. Typically uses ou=people.
# search_base_dns = ["ou=people,dc=grafana,dc=org"]
#
# Group search filter, to retrieve the groups of which the user is a member
# group_search_filter = "(&(objectClass=posixGroup)(memberUid=%s))"
#
# An array of the base DNs to search through for groups. Typically uses ou=groups
# group_search_base_dns = ["ou=groups,dc=grafana,dc=org"]
# Specify names of the ldap attributes your ldap uses # Specify names of the ldap attributes your ldap uses
[servers.attributes] [servers.attributes]
name = "givenName" name = "givenName"
......
...@@ -311,18 +311,51 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) { ...@@ -311,18 +311,51 @@ func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) {
return nil, errors.New("Ldap search matched more than one entry, please review your filter setting") return nil, errors.New("Ldap search matched more than one entry, please review your filter setting")
} }
var memberOf []string
if a.server.GroupSearchFilter == "" {
memberOf = getLdapAttrArray(a.server.Attr.MemberOf, searchResult)
} else {
// If we are using a POSIX LDAP schema it won't support memberOf, so we manually search the groups
var groupSearchResult *ldap.SearchResult
for _, groupSearchBase := range a.server.GroupSearchBaseDNs {
filter := strings.Replace(a.server.GroupSearchFilter, "%s", username, -1)
groupSearchReq := ldap.SearchRequest{
BaseDN: groupSearchBase,
Scope: ldap.ScopeWholeSubtree,
DerefAliases: ldap.NeverDerefAliases,
Attributes: []string{
// Here MemberOf would be the thing that identifies the group, which is normally 'cn'
a.server.Attr.MemberOf,
},
Filter: filter,
}
groupSearchResult, err = a.conn.Search(&groupSearchReq)
if err != nil {
return nil, err
}
if len(groupSearchResult.Entries) > 0 {
for i := range groupSearchResult.Entries {
memberOf = append(memberOf, getLdapAttrN(a.server.Attr.MemberOf, groupSearchResult, i))
}
break
}
}
}
return &ldapUserInfo{ return &ldapUserInfo{
DN: searchResult.Entries[0].DN, DN: searchResult.Entries[0].DN,
LastName: getLdapAttr(a.server.Attr.Surname, searchResult), LastName: getLdapAttr(a.server.Attr.Surname, searchResult),
FirstName: getLdapAttr(a.server.Attr.Name, searchResult), FirstName: getLdapAttr(a.server.Attr.Name, searchResult),
Username: getLdapAttr(a.server.Attr.Username, searchResult), Username: getLdapAttr(a.server.Attr.Username, searchResult),
Email: getLdapAttr(a.server.Attr.Email, searchResult), Email: getLdapAttr(a.server.Attr.Email, searchResult),
MemberOf: getLdapAttrArray(a.server.Attr.MemberOf, searchResult), MemberOf: memberOf,
}, nil }, nil
} }
func getLdapAttr(name string, result *ldap.SearchResult) string { func getLdapAttrN(name string, result *ldap.SearchResult, n int) string {
for _, attr := range result.Entries[0].Attributes { for _, attr := range result.Entries[n].Attributes {
if attr.Name == name { if attr.Name == name {
if len(attr.Values) > 0 { if len(attr.Values) > 0 {
return attr.Values[0] return attr.Values[0]
...@@ -332,6 +365,10 @@ func getLdapAttr(name string, result *ldap.SearchResult) string { ...@@ -332,6 +365,10 @@ func getLdapAttr(name string, result *ldap.SearchResult) string {
return "" return ""
} }
func getLdapAttr(name string, result *ldap.SearchResult) string {
return getLdapAttrN(name, result, 0)
}
func getLdapAttrArray(name string, result *ldap.SearchResult) []string { func getLdapAttrArray(name string, result *ldap.SearchResult) []string {
for _, attr := range result.Entries[0].Attributes { for _, attr := range result.Entries[0].Attributes {
if attr.Name == name { if attr.Name == name {
......
...@@ -27,6 +27,9 @@ type LdapServerConf struct { ...@@ -27,6 +27,9 @@ type LdapServerConf struct {
SearchFilter string `toml:"search_filter"` SearchFilter string `toml:"search_filter"`
SearchBaseDNs []string `toml:"search_base_dns"` SearchBaseDNs []string `toml:"search_base_dns"`
GroupSearchFilter string `toml:"group_search_filter"`
GroupSearchBaseDNs []string `toml:"group_search_base_dns"`
LdapGroups []*LdapGroupToOrgRole `toml:"group_mappings"` LdapGroups []*LdapGroupToOrgRole `toml:"group_mappings"`
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment