package ad import ( "gopkg.in/ldap.v2" "k8s.io/kubernetes/pkg/util/sets" "github.com/openshift/origin/pkg/auth/ldaputil" "github.com/openshift/origin/pkg/auth/ldaputil/ldapclient" "github.com/openshift/origin/pkg/cmd/admin/groups/sync/groupdetector" "github.com/openshift/origin/pkg/cmd/admin/groups/sync/interfaces" ) // NewADLDAPInterface builds a new ADLDAPInterface using a schema-appropriate config func NewADLDAPInterface(clientConfig ldapclient.Config, userQuery ldaputil.LDAPQuery, groupMembershipAttributes []string, userNameAttributes []string) *ADLDAPInterface { return &ADLDAPInterface{ clientConfig: clientConfig, userQuery: userQuery, userNameAttributes: userNameAttributes, groupMembershipAttributes: groupMembershipAttributes, ldapGroupToLDAPMembers: map[string][]*ldap.Entry{}, } } // ADLDAPInterface extracts the member list of an LDAP group entry from an LDAP server // with first-class LDAP entries for user only. The ADLDAPInterface is *NOT* thread-safe. type ADLDAPInterface struct { // clientConfig holds LDAP connection information clientConfig ldapclient.Config // userQuery holds the information necessary to make an LDAP query for all first-class user entries on the LDAP server userQuery ldaputil.LDAPQuery // groupMembershipAttributes defines which attributes on an LDAP user entry will be interpreted as its ldapGroupUID groupMembershipAttributes []string // UserNameAttributes defines which attributes on an LDAP user entry will be interpreted as its name userNameAttributes []string // cacheFullyPopulated determines if the cache has been fully populated // populateCache() will populate it fully, specific calls to ExtractMembers() will not cacheFullyPopulated bool // ldapGroupToLDAPMembers holds the result of user queries for later reference, indexed on group UID // e.g. this will map all LDAP users to the LDAP group UID whose entry returned them ldapGroupToLDAPMembers map[string][]*ldap.Entry } // The LDAPInterface must conform to the following interfaces var _ interfaces.LDAPMemberExtractor = &ADLDAPInterface{} var _ interfaces.LDAPGroupLister = &ADLDAPInterface{} // ExtractMembers returns the LDAP member entries for a group specified with a ldapGroupUID func (e *ADLDAPInterface) ExtractMembers(ldapGroupUID string) ([]*ldap.Entry, error) { // if we already have it cached, return the cached value if members, present := e.ldapGroupToLDAPMembers[ldapGroupUID]; present { return members, nil } // This happens in cases where we did not list out every group. In that case, we're going to be asked about specific groups. usersInGroup := []*ldap.Entry{} // check for all users with ldapGroupUID in any of the allowed member attributes for _, currAttribute := range e.groupMembershipAttributes { currQuery := ldaputil.LDAPQueryOnAttribute{LDAPQuery: e.userQuery, QueryAttribute: currAttribute} searchRequest, err := currQuery.NewSearchRequest(ldapGroupUID, e.requiredUserAttributes()) if err != nil { return nil, err } currEntries, err := ldaputil.QueryForEntries(e.clientConfig, searchRequest) if err != nil { return nil, err } for _, currEntry := range currEntries { if !isEntryPresent(usersInGroup, currEntry) { usersInGroup = append(usersInGroup, currEntry) } } } e.ldapGroupToLDAPMembers[ldapGroupUID] = usersInGroup return usersInGroup, nil } // ListGroups queries for all groups as configured with the common group filter and returns their // LDAP group UIDs. This also satisfies the LDAPGroupLister interface func (e *ADLDAPInterface) ListGroups() ([]string, error) { if err := e.populateCache(); err != nil { return nil, err } return sets.StringKeySet(e.ldapGroupToLDAPMembers).List(), nil } // populateCache queries all users to build a map of all the groups. If the cache has already been // populated, this is a no-op. func (e *ADLDAPInterface) populateCache() error { if e.cacheFullyPopulated { return nil } searchRequest := e.userQuery.NewSearchRequest(e.requiredUserAttributes()) userEntries, err := ldaputil.QueryForEntries(e.clientConfig, searchRequest) if err != nil { return err } for _, userEntry := range userEntries { if userEntry == nil { continue } for _, groupAttribute := range e.groupMembershipAttributes { for _, groupUID := range userEntry.GetAttributeValues(groupAttribute) { if _, exists := e.ldapGroupToLDAPMembers[groupUID]; !exists { e.ldapGroupToLDAPMembers[groupUID] = []*ldap.Entry{} } if !isEntryPresent(e.ldapGroupToLDAPMembers[groupUID], userEntry) { e.ldapGroupToLDAPMembers[groupUID] = append(e.ldapGroupToLDAPMembers[groupUID], userEntry) } } } } e.cacheFullyPopulated = true return nil } func isEntryPresent(haystack []*ldap.Entry, needle *ldap.Entry) bool { for _, curr := range haystack { if curr.DN == needle.DN { return true } } return false } func (e *ADLDAPInterface) requiredUserAttributes() []string { allAttributes := sets.NewString(e.userNameAttributes...) allAttributes.Insert(e.groupMembershipAttributes...) return allAttributes.List() } // Exists determines if a group idenified with its LDAP group UID exists on the LDAP server func (e *ADLDAPInterface) Exists(ldapGrouUID string) (bool, error) { return groupdetector.NewMemberBasedDetector(e).Exists(ldapGrouUID) }