/* * Returns the DN of the object representing the authenticated user. */ func (b *backend) getUserDN(cfg *ConfigEntry, c *ldap.Conn, bindDN string) (string, error) { userDN := "" if cfg.UPNDomain != "" { // Find the distinguished name for the user if userPrincipalName used for login filter := fmt.Sprintf("(userPrincipalName=%s)", ldap.EscapeFilter(bindDN)) if b.Logger().IsDebug() { b.Logger().Debug("auth/ldap: Searching UPN", "userdn", cfg.UserDN, "filter", filter) } result, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.UserDN, Scope: 2, // subtree Filter: filter, }) if err != nil { return userDN, fmt.Errorf("LDAP search failed for detecting user: %v", err) } for _, e := range result.Entries { userDN = e.DN } } else { userDN = bindDN } return userDN, nil }
func getBindDN(cfg *ConfigEntry, c *ldap.Conn, username string) (string, error) { bindDN := "" if cfg.DiscoverDN || (cfg.BindDN != "" && cfg.BindPassword != "") { if err := c.Bind(cfg.BindDN, cfg.BindPassword); err != nil { return bindDN, fmt.Errorf("LDAP bind (service) failed: %v", err) } result, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.UserDN, Scope: 2, // subtree Filter: fmt.Sprintf("(%s=%s)", cfg.UserAttr, ldap.EscapeFilter(username)), }) if err != nil { return bindDN, fmt.Errorf("LDAP search for binddn failed: %v", err) } if len(result.Entries) != 1 { return bindDN, fmt.Errorf("LDAP search for binddn 0 or not unique") } bindDN = result.Entries[0].DN } else { if cfg.UPNDomain != "" { bindDN = fmt.Sprintf("%s@%s", EscapeLDAPValue(username), cfg.UPNDomain) } else { bindDN = fmt.Sprintf("%s=%s,%s", cfg.UserAttr, EscapeLDAPValue(username), cfg.UserDN) } } return bindDN, nil }
func (ad *ActiveDirectoryClient) getUserSearchFilter(samAccountName string) string { userFilter := fmt.Sprintf("(&(%s=%s)%s)", AD_USER_ATTRIBUTE, ldap.EscapeFilter(samAccountName), AD_USER_FILTER) log.V(11).Infof("Filter: %s\n", userFilter) return userFilter }
// buildAttributeQuery builds the query containing a filter that conjoins the common filter given // in the configuration with the specific attribute filter for which the attribute value is given func (o *LDAPQueryOnAttribute) buildAttributeQuery(attributeValue string, attributes []string) *ldap.SearchRequest { specificFilter := fmt.Sprintf("%s=%s", ldap.EscapeFilter(o.QueryAttribute), ldap.EscapeFilter(attributeValue)) filter := fmt.Sprintf("(&(%s)(%s))", o.Filter, specificFilter) return ldap.NewSearchRequest( o.BaseDN, int(o.Scope), int(o.DerefAliases), 0, // allowed return size - indicates no limit o.TimeLimit, false, // not types only filter, attributes, nil, // no controls ) }
func getUserDN(cfg *ConfigEntry, c *ldap.Conn, bindDN string) (string, error) { userDN := "" if cfg.UPNDomain != "" { // Find the distinguished name for the user if userPrincipalName used for login result, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.UserDN, Scope: 2, // subtree Filter: fmt.Sprintf("(userPrincipalName=%s)", ldap.EscapeFilter(bindDN)), }) if err != nil { return userDN, fmt.Errorf("LDAP search failed for detecting user: %v", err) } for _, e := range result.Entries { userDN = e.DN } } else { userDN = bindDN } return userDN, nil }
// getIdentity looks up a username in an LDAP server, and attempts to bind to the user's DN using the provided password func (a *Authenticator) getIdentity(username, password string) (authapi.UserIdentityInfo, bool, error) { defer func() { if e := recover(); e != nil { util.HandleError(fmt.Errorf("Recovered panic: %v, %s", e, debug.Stack())) } }() if len(username) == 0 || len(password) == 0 { return nil, false, nil } // Make the connection l, err := a.connect() if err != nil { return nil, false, err } defer l.Close() // If specified, bind the username/password for search phase if len(a.options.BindDN) > 0 { if err := l.Bind(a.options.BindDN, a.options.BindPassword); err != nil { return nil, false, err } } // & together the filter specified in the LDAP options with the user-specific filter filter := fmt.Sprintf("(&%s(%s=%s))", a.options.URL.Filter, ldap.EscapeFilter(a.options.URL.QueryAttribute), ldap.EscapeFilter(username), ) // Build list of attributes to retrieve attrs := util.NewStringSet(a.options.URL.QueryAttribute) attrs.Insert(a.options.AttributeEmail...) attrs.Insert(a.options.AttributeName...) attrs.Insert(a.options.AttributePreferredUsername...) attrs.Insert(a.options.AttributeID...) // Search for LDAP record searchRequest := ldap.NewSearchRequest( a.options.URL.BaseDN, // base dn int(a.options.URL.Scope), // scope ldap.NeverDerefAliases, // deref 2, // size limit, we want to know if this is not unique, but don't want the entire tree 0, // no client-specified time limit, determined by LDAP server. TODO: make configurable? false, // not types only filter, // filter attrs.List(), // attributes to retrieve nil, // controls ) glog.V(4).Infof("searching for %s", filter) results, err := l.Search(searchRequest) if err != nil { return nil, false, err } if len(results.Entries) == 0 { // 0 results means a missing username, not an error glog.V(4).Infof("no entries matching %s", filter) return nil, false, nil } if len(results.Entries) > 1 { // More than 1 result means a misconfigured server filter or query parameter return nil, false, fmt.Errorf("multiple entries found matching %q", username) } entry := results.Entries[0] glog.V(4).Infof("found dn=%q for %s", entry.DN, filter) // Bind with given username and password to attempt to authenticate if err := l.Bind(entry.DN, password); err != nil { glog.V(4).Infof("error binding password for %q: %v", entry.DN, err) if err, ok := err.(*ldap.Error); ok { switch err.ResultCode { case ldap.LDAPResultInappropriateAuthentication: // inappropriateAuthentication (48) // Indicates the server requires the client that had attempted // to bind anonymously or without supplying credentials to // provide some form of credentials. fallthrough case ldap.LDAPResultInvalidCredentials: // invalidCredentials (49) // Indicates that the provided credentials (e.g., the user's name // and password) are invalid. // Authentication failed, return false, but no error return nil, false, nil } } return nil, false, err } // Build the identity uid := getAttributeValue(entry, a.options.AttributeID) if uid == "" { return nil, false, fmt.Errorf("Could not retrieve a non-empty value from %v attributes for dn=%q", a.options.AttributeID, entry.DN) } identity := authapi.NewDefaultUserIdentityInfo(a.providerName, uid) // Add optional extra attributes if present for k, attrs := range map[string][]string{ authapi.IdentityPreferredUsernameKey: a.options.AttributePreferredUsername, authapi.IdentityEmailKey: a.options.AttributeEmail, authapi.IdentityDisplayNameKey: a.options.AttributeName, } { if v := getAttributeValue(entry, attrs); len(v) != 0 { identity.Extra[k] = v } } return identity, true, nil }
func (a *ldapAuther) searchForUser(username string) (*ldapUserInfo, error) { var searchResult *ldap.SearchResult var err error for _, searchBase := range a.server.SearchBaseDNs { searchReq := ldap.SearchRequest{ BaseDN: searchBase, Scope: ldap.ScopeWholeSubtree, DerefAliases: ldap.NeverDerefAliases, Attributes: []string{ a.server.Attr.Username, a.server.Attr.Surname, a.server.Attr.Email, a.server.Attr.Name, a.server.Attr.MemberOf, }, Filter: strings.Replace(a.server.SearchFilter, "%s", ldap.EscapeFilter(username), -1), } searchResult, err = a.conn.Search(&searchReq) if err != nil { return nil, err } if len(searchResult.Entries) > 0 { break } } if len(searchResult.Entries) == 0 { return nil, ErrInvalidCredentials } if len(searchResult.Entries) > 1 { 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 { var filter_replace string filter_replace = getLdapAttr(a.server.GroupSearchFilterUserAttribute, searchResult) if a.server.GroupSearchFilterUserAttribute == "" { filter_replace = getLdapAttr(a.server.Attr.Username, searchResult) } filter := strings.Replace(a.server.GroupSearchFilter, "%s", ldap.EscapeFilter(filter_replace), -1) if ldapCfg.VerboseLogging { log.Info("LDAP: Searching for user's groups: %s", filter) } 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{ DN: searchResult.Entries[0].DN, LastName: getLdapAttr(a.server.Attr.Surname, searchResult), FirstName: getLdapAttr(a.server.Attr.Name, searchResult), Username: getLdapAttr(a.server.Attr.Username, searchResult), Email: getLdapAttr(a.server.Attr.Email, searchResult), MemberOf: memberOf, }, nil }
func getLdapGroups(cfg *ConfigEntry, c *ldap.Conn, userDN string, username string) ([]string, error) { // retrieve the groups in a string/bool map as a structure to avoid duplicates inside ldapMap := make(map[string]bool) // Fetch the optional memberOf property values on the user object // This is the most common method used in Active Directory setup to retrieve the groups result, err := c.Search(&ldap.SearchRequest{ BaseDN: userDN, Scope: 0, // base scope to fetch only the userDN Filter: "(cn=*)", // bogus filter, required to fetch the CN from userDN Attributes: []string{ "memberOf", }, }) // this check remains in case something happens with the ldap query or connection if err != nil { return nil, fmt.Errorf("LDAP fetch of distinguishedName=%s failed: %v", userDN, err) } // if there are more than one entry, we consider the results irrelevant and ignore them if len(result.Entries) == 1 { for _, attr := range result.Entries[0].Attributes { // Find the groups the user is member of from the 'memberOf' attribute extracting the CN if attr.Name == "memberOf" { for _, value := range attr.Values { memberOfDN, err := ldap.ParseDN(value) if err != nil || len(memberOfDN.RDNs) == 0 { continue } for _, rdn := range memberOfDN.RDNs { for _, rdnTypeAndValue := range rdn.Attributes { if strings.EqualFold(rdnTypeAndValue.Type, "CN") { ldapMap[rdnTypeAndValue.Value] = true } } } } } } } // Find groups by searching in groupDN for any of the memberUid, member or uniqueMember attributes // and retrieving the CN in the DN result if cfg.GroupDN != "" { result, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.GroupDN, Scope: 2, // subtree Filter: fmt.Sprintf("(|(memberUid=%s)(member=%s)(uniqueMember=%s))", ldap.EscapeFilter(username), ldap.EscapeFilter(userDN), ldap.EscapeFilter(userDN)), }) if err != nil { return nil, fmt.Errorf("LDAP search failed: %v", err) } for _, e := range result.Entries { dn, err := ldap.ParseDN(e.DN) if err != nil || len(dn.RDNs) == 0 { continue } for _, rdn := range dn.RDNs { for _, rdnTypeAndValue := range rdn.Attributes { if strings.EqualFold(rdnTypeAndValue.Type, "CN") { ldapMap[rdnTypeAndValue.Value] = true } } } } } ldapGroups := make([]string, len(ldapMap)) for key, _ := range ldapMap { ldapGroups = append(ldapGroups, key) } return ldapGroups, nil }
// getIdentity looks up a username in an LDAP server, and attempts to bind to the user's DN using the provided password func (a *Authenticator) getIdentity(username, password string) (authapi.UserIdentityInfo, bool, error) { defer func() { if e := recover(); e != nil { util.HandleError(fmt.Errorf("Recovered panic: %v, %s", e, debug.Stack())) } }() if len(username) == 0 || len(password) == 0 { return nil, false, nil } // Make the connection and bind to it if a bind DN and password were given l, err := a.options.ClientConfig.Connect() if err != nil { return nil, false, err } defer l.Close() if _, err := a.options.ClientConfig.Bind(l); err != nil { return nil, false, err } // & together the filter specified in the LDAP options with the user-specific filter filter := fmt.Sprintf("(&%s(%s=%s))", a.options.URL.Filter, ldap.EscapeFilter(a.options.URL.QueryAttribute), ldap.EscapeFilter(username), ) // Build list of attributes to retrieve attrs := sets.NewString(a.options.URL.QueryAttribute) attrs.Insert(a.options.UserAttributeDefiner.AllAttributes().List()...) // Search for LDAP record searchRequest := ldap.NewSearchRequest( a.options.URL.BaseDN, // base dn int(a.options.URL.Scope), // scope ldap.NeverDerefAliases, // deref 2, // size limit, we want to know if this is not unique, but don't want the entire tree 0, // no client-specified time limit, determined by LDAP server. TODO: make configurable? false, // not types only filter, // filter attrs.List(), // attributes to retrieve nil, // controls ) glog.V(4).Infof("searching for %s", filter) results, err := l.Search(searchRequest) if err != nil { return nil, false, err } if len(results.Entries) == 0 { // 0 results means a missing username, not an error glog.V(4).Infof("no entries matching %s", filter) return nil, false, nil } if len(results.Entries) > 1 { // More than 1 result means a misconfigured server filter or query parameter return nil, false, fmt.Errorf("multiple entries found matching %q", username) } entry := results.Entries[0] glog.V(4).Infof("found dn=%q for %s", entry.DN, filter) // Bind with given username and password to attempt to authenticate if err := l.Bind(entry.DN, password); err != nil { glog.V(4).Infof("error binding password for %q: %v", entry.DN, err) if err, ok := err.(*ldap.Error); ok { switch err.ResultCode { case ldap.LDAPResultInappropriateAuthentication: // inappropriateAuthentication (48) // Indicates the server requires the client that had attempted // to bind anonymously or without supplying credentials to // provide some form of credentials. fallthrough case ldap.LDAPResultInvalidCredentials: // invalidCredentials (49) // Indicates that the provided credentials (e.g., the user's name // and password) are invalid. // Authentication failed, return false, but no error return nil, false, nil } } return nil, false, err } // Build the identity identity, err := a.identityFactory.IdentityFor(entry) if err != nil { return nil, false, err } return identity, true, nil }
/* * getLdapGroups queries LDAP and returns a slice describing the set of groups the authenticated user is a member of. * * The search query is constructed according to cfg.GroupFilter, and run in context of cfg.GroupDN. * Groups will be resolved from the query results by following the attribute defined in cfg.GroupAttr. * * cfg.GroupFilter is a go template and is compiled with the following context: [UserDN, Username] * UserDN - The DN of the authenticated user * Username - The Username of the authenticated user * * Example: * cfg.GroupFilter = "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))" * cfg.GroupDN = "OU=Groups,DC=myorg,DC=com" * cfg.GroupAttr = "cn" * * NOTE - If cfg.GroupFilter is empty, no query is performed and an empty result slice is returned. * */ func (b *backend) getLdapGroups(cfg *ConfigEntry, c *ldap.Conn, userDN string, username string) ([]string, error) { // retrieve the groups in a string/bool map as a structure to avoid duplicates inside ldapMap := make(map[string]bool) if cfg.GroupFilter == "" { b.Logger().Warn("auth/ldap: GroupFilter is empty, will not query server") return make([]string, 0), nil } if cfg.GroupDN == "" { b.Logger().Warn("auth/ldap: GroupDN is empty, will not query server") return make([]string, 0), nil } // If groupfilter was defined, resolve it as a Go template and use the query for // returning the user's groups if b.Logger().IsDebug() { b.Logger().Debug("auth/ldap: Compiling group filter", "group_filter", cfg.GroupFilter) } // Parse the configuration as a template. // Example template "(&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))" t, err := template.New("queryTemplate").Parse(cfg.GroupFilter) if err != nil { return nil, fmt.Errorf("LDAP search failed due to template compilation error: %v", err) } // Build context to pass to template - we will be exposing UserDn and Username. context := struct { UserDN string Username string }{ ldap.EscapeFilter(userDN), ldap.EscapeFilter(username), } var renderedQuery bytes.Buffer t.Execute(&renderedQuery, context) if b.Logger().IsDebug() { b.Logger().Debug("auth/ldap: Searching", "groupdn", cfg.GroupDN, "rendered_query", renderedQuery.String()) } result, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.GroupDN, Scope: 2, // subtree Filter: renderedQuery.String(), Attributes: []string{ cfg.GroupAttr, }, }) if err != nil { return nil, fmt.Errorf("LDAP search failed: %v", err) } for _, e := range result.Entries { dn, err := ldap.ParseDN(e.DN) if err != nil || len(dn.RDNs) == 0 { continue } // Enumerate attributes of each result, parse out CN and add as group values := e.GetAttributeValues(cfg.GroupAttr) if len(values) > 0 { for _, val := range values { groupCN := b.getCN(val) ldapMap[groupCN] = true } } else { // If groupattr didn't resolve, use self (enumerating group objects) groupCN := b.getCN(e.DN) ldapMap[groupCN] = true } } ldapGroups := make([]string, 0, len(ldapMap)) for key, _ := range ldapMap { ldapGroups = append(ldapGroups, key) } return ldapGroups, nil }
func (lc *LDAPClient) getUserSearchFilter(samAccountName string) string { userFilter := fmt.Sprintf("(&(%s=%s)%s)", AD_USER_ATTRIBUTE, ldap.EscapeFilter(samAccountName), AD_USER_FILTER) return userFilter }
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, error) { cfg, err := b.Config(req) if err != nil { return nil, nil, err } if cfg == nil { return nil, logical.ErrorResponse("ldap backend not configured"), nil } c, err := cfg.DialLDAP() if err != nil { return nil, logical.ErrorResponse(err.Error()), nil } binddn := "" if cfg.DiscoverDN || (cfg.BindDN != "" && cfg.BindPassword != "") { if err = c.Bind(cfg.BindDN, cfg.BindPassword); err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind (service) failed: %v", err)), nil } sresult, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.UserDN, Scope: 2, // subtree Filter: fmt.Sprintf("(%s=%s)", cfg.UserAttr, ldap.EscapeFilter(username)), }) if err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search for binddn failed: %v", err)), nil } if len(sresult.Entries) != 1 { return nil, logical.ErrorResponse("LDAP search for binddn 0 or not uniq"), nil } binddn = sresult.Entries[0].DN } else { if cfg.UPNDomain != "" { binddn = fmt.Sprintf("%s@%s", EscapeLDAPValue(username), cfg.UPNDomain) } else { binddn = fmt.Sprintf("%s=%s,%s", cfg.UserAttr, EscapeLDAPValue(username), cfg.UserDN) } } if err = c.Bind(binddn, password); err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind failed: %v", err)), nil } userdn := "" if cfg.UPNDomain != "" { // Find the distinguished name for the user if userPrincipalName used for login sresult, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.UserDN, Scope: 2, // subtree Filter: fmt.Sprintf("(userPrincipalName=%s)", ldap.EscapeFilter(binddn)), }) if err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil } for _, e := range sresult.Entries { userdn = e.DN } } else { userdn = binddn } // Enumerate all groups the user is member of. The search filter should // work with both openldap and MS AD standard schemas. sresult, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.GroupDN, Scope: 2, // subtree Filter: fmt.Sprintf("(|(memberUid=%s)(member=%s)(uniqueMember=%s))", ldap.EscapeFilter(username), ldap.EscapeFilter(userdn), ldap.EscapeFilter(userdn)), }) if err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil } var allgroups []string var policies []string user, err := b.User(req.Storage, username) if err == nil && user != nil { allgroups = append(allgroups, user.Groups...) } for _, e := range sresult.Entries { dn, err := ldap.ParseDN(e.DN) if err != nil || len(dn.RDNs) == 0 || len(dn.RDNs[0].Attributes) == 0 { continue } gname := dn.RDNs[0].Attributes[0].Value allgroups = append(allgroups, gname) } for _, gname := range allgroups { group, err := b.Group(req.Storage, gname) if err == nil && group != nil { policies = append(policies, group.Policies...) } } if len(policies) == 0 { return nil, logical.ErrorResponse("user is not member of any authorized group"), nil } return policies, nil, nil }
func (b *backend) Login(req *logical.Request, username string, password string) ([]string, *logical.Response, error) { cfg, err := b.Config(req) if err != nil { return nil, nil, err } if cfg == nil { return nil, logical.ErrorResponse("ldap backend not configured"), nil } c, err := cfg.DialLDAP() if err != nil { return nil, logical.ErrorResponse(err.Error()), nil } if c == nil { return nil, logical.ErrorResponse("invalid connection returned from LDAP dial"), nil } binddn := "" if cfg.DiscoverDN || (cfg.BindDN != "" && cfg.BindPassword != "") { if err = c.Bind(cfg.BindDN, cfg.BindPassword); err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind (service) failed: %v", err)), nil } sresult, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.UserDN, Scope: 2, // subtree Filter: fmt.Sprintf("(%s=%s)", cfg.UserAttr, ldap.EscapeFilter(username)), }) if err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search for binddn failed: %v", err)), nil } if len(sresult.Entries) != 1 { return nil, logical.ErrorResponse("LDAP search for binddn 0 or not uniq"), nil } binddn = sresult.Entries[0].DN } else { if cfg.UPNDomain != "" { binddn = fmt.Sprintf("%s@%s", EscapeLDAPValue(username), cfg.UPNDomain) } else { binddn = fmt.Sprintf("%s=%s,%s", cfg.UserAttr, EscapeLDAPValue(username), cfg.UserDN) } } if err = c.Bind(binddn, password); err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP bind failed: %v", err)), nil } userdn := "" if cfg.UPNDomain != "" { // Find the distinguished name for the user if userPrincipalName used for login sresult, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.UserDN, Scope: 2, // subtree Filter: fmt.Sprintf("(userPrincipalName=%s)", ldap.EscapeFilter(binddn)), }) if err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil } for _, e := range sresult.Entries { userdn = e.DN } } else { userdn = binddn } var allgroups []string var policies []string resp := &logical.Response{ Data: map[string]interface{}{}, } // Fetch custom (local) groups the user has been added to user, err := b.User(req.Storage, username) if err == nil && user != nil { allgroups = append(allgroups, user.Groups...) } if cfg.GroupDN != "" { // Enumerate all groups the user is member of. The search filter should // work with both openldap and MS AD standard schemas. sresult, err := c.Search(&ldap.SearchRequest{ BaseDN: cfg.GroupDN, Scope: 2, // subtree Filter: fmt.Sprintf("(|(memberUid=%s)(member=%s)(uniqueMember=%s))", ldap.EscapeFilter(username), ldap.EscapeFilter(userdn), ldap.EscapeFilter(userdn)), }) if err != nil { return nil, logical.ErrorResponse(fmt.Sprintf("LDAP search failed: %v", err)), nil } for _, e := range sresult.Entries { dn, err := ldap.ParseDN(e.DN) if err != nil || len(dn.RDNs) == 0 || len(dn.RDNs[0].Attributes) == 0 { continue } gname := dn.RDNs[0].Attributes[0].Value allgroups = append(allgroups, gname) } } else { resp.AddWarning("no group DN configured; only policies from locally-defined groups available") } for _, gname := range allgroups { group, err := b.Group(req.Storage, gname) if err == nil && group != nil { policies = append(policies, group.Policies...) } } if len(policies) == 0 { errStr := "user is not a member of any authorized group" if len(resp.Warnings()) > 0 { errStr = fmt.Sprintf("%s; additionally, %s", errStr, resp.Warnings()[0]) } resp.Data["error"] = errStr return nil, resp, nil } return policies, resp, nil }