lib/github/ldap/member_search/recursive.rb in github-ldap-1.7.1 vs lib/github/ldap/member_search/recursive.rb in github-ldap-1.8.0
- old
+ new
@@ -32,76 +32,101 @@
# Public: Performs search for group members, including groups and
# members of subgroups recursively.
#
# Returns Array of Net::LDAP::Entry objects.
def perform(group)
+ # track groups found
found = Hash.new
- # find members (N queries)
- entries = member_entries(group)
- return [] if entries.empty?
+ # track all DNs searched for (so we don't repeat searches)
+ searched = Set.new
- # track found entries
- entries.each do |entry|
- found[entry.dn] = entry
+ # if this is a posixGroup, return members immediately (no nesting)
+ uids = member_uids(group)
+ return entries_by_uid(uids) if uids.any?
+
+ # track group
+ searched << group.dn
+ found[group.dn] = group
+
+ # pull out base group's member DNs
+ dns = member_dns(group)
+
+ # search for base group's subgroups
+ groups = dns.each_with_object([]) do |dn, groups|
+ groups.concat find_groups_by_dn(dn)
+ searched << dn
end
- # descend to `depth` levels, at most
- depth.times do |n|
- # find every (new, unique) member entry
- depth_subentries = entries.each_with_object([]) do |entry, depth_entries|
- submembers = entry["member"]
+ # track found groups
+ groups.each { |g| found[g.dn] = g }
- # skip any members we've already found
- submembers.reject! { |dn| found.key?(dn) }
+ # recursively find subgroups
+ unless groups.empty?
+ depth.times do |n|
+ # pull out subgroups' member DNs to search through
+ sub_dns = groups.each_with_object([]) do |subgroup, sub_dns|
+ sub_dns.concat member_dns(subgroup)
+ end
- # find members of subgroup, including subgroups (N queries)
- subentries = member_entries(entry)
- next if subentries.empty?
+ # filter out if already searched for
+ sub_dns.reject! { |dn| searched.include?(dn) }
- # track found subentries
- subentries.each { |entry| found[entry.dn] = entry }
+ # give up if there's nothing else to search for
+ break if sub_dns.empty?
- # collect all entries for this depth
- depth_entries.concat subentries
- end
+ # search for subgroups
+ subgroups = sub_dns.each_with_object([]) do |dn, subgroups|
+ subgroups.concat find_groups_by_dn(dn)
+ searched << dn
+ end
- # stop if there are no more subgroups to search
- break if depth_subentries.empty?
+ # give up if there were no subgroups found
+ break if subgroups.empty?
- # go one level deeper
- entries = depth_subentries
+ # track found subgroups
+ subgroups.each { |g| found[g.dn] = g }
+
+ # descend another level
+ groups = subgroups
+ end
end
- # return all found entries
- found.values
- end
+ # entries to return
+ entries = []
- # Internal: Fetch member entries, including subgroups, for the given
- # entry.
- #
- # Returns an Array of Net::LDAP::Entry objects.
- def member_entries(entry)
- entries = []
- dns = member_dns(entry)
- uids = member_uids(entry)
+ # collect all member DNs, discarding dupes and subgroup DNs
+ members = found.values.each_with_object([]) do |group, dns|
+ entries << group
+ dns.concat member_dns(group)
+ end.uniq.reject { |dn| found.key?(dn) }
- entries.concat entries_by_uid(uids) unless uids.empty?
- entries.concat entries_by_dn(dns) unless dns.empty?
+ # wrap member DNs in Net::LDAP::Entry objects
+ entries.concat members.map! { |dn| Net::LDAP::Entry.new(dn) }
entries
end
- private :member_entries
- # Internal: Bind a list of DNs to their respective entries.
+ # Internal: Search for Groups by DN.
#
- # Returns an Array of Net::LDAP::Entry objects.
- def entries_by_dn(members)
- members.map do |dn|
- ldap.domain(dn).bind(attributes: attrs)
- end.compact
+ # Given a Distinguished Name (DN) String value, find the Group entry
+ # that matches it. The DN may map to a `person` entry, but we want to
+ # filter those out.
+ #
+ # This will find zero or one entry most of the time, but it's not
+ # guaranteed so we account for the possibility of more.
+ #
+ # This method is intended to be used with `Array#concat` by the caller.
+ #
+ # Returns an Array of zero or more Net::LDAP::Entry objects.
+ def find_groups_by_dn(dn)
+ ldap.search \
+ base: dn,
+ scope: Net::LDAP::SearchScope_BaseObject,
+ attributes: attrs,
+ filter: ALL_GROUPS_FILTER
end
- private :entries_by_dn
+ private :find_groups_by_dn
# Internal: Fetch entries by UID.
#
# Returns an Array of Net::LDAP::Entry objects.
def entries_by_uid(members)