class GitHub::Ldap::MemberSearch::Recursive

Look up group members recursively.

This results in a maximum of `depth` iterations/recursions to look up members of a group and its subgroups.

Constants

DEFAULT_ATTRS
DEFAULT_MAX_DEPTH

Attributes

attrs[R]

Internal: The attributes to search for.

depth[R]

Internal: The maximum depth to search for members.

Public Class Methods

new(ldap, options = {}) click to toggle source

Public: Instantiate new search strategy.

NOTE: This overrides default behavior to configure `depth` and `attrs`.

Calls superclass method GitHub::Ldap::MemberSearch::Base::new
# File lib/github/ldap/member_search/recursive.rb, line 26
def initialize(ldap, options = {})
  super
  @depth = options[:depth] || DEFAULT_MAX_DEPTH
  @attrs = Array(options[:attrs]).concat DEFAULT_ATTRS
end

Public Instance Methods

perform(group) click to toggle source

Public: Performs search for group members, including groups and members of subgroups recursively.

Returns Array of Net::LDAP::Entry objects.

# File lib/github/ldap/member_search/recursive.rb, line 36
def perform(group)
  # track groups found
  found = Hash.new

  # track all DNs searched for (so we don't repeat searches)
  searched = Set.new

  # 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

  # track found groups
  groups.each { |g| found[g.dn] = g }

  # 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

      # filter out if already searched for
      sub_dns.reject! { |dn| searched.include?(dn) }

      # give up if there's nothing else to search for
      break if sub_dns.empty?

      # search for subgroups
      subgroups = sub_dns.each_with_object([]) do |dn, subgroups|
        subgroups.concat find_groups_by_dn(dn)
        searched << dn
      end

      # give up if there were no subgroups found
      break if subgroups.empty?

      # track found subgroups
      subgroups.each { |g| found[g.dn] = g }

      # descend another level
      groups = subgroups
    end
  end

  # entries to return
  entries  = []

  # 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) }

  # wrap member DNs in Net::LDAP::Entry objects
  entries.concat members.map! { |dn| Net::LDAP::Entry.new(dn) }

  entries
end

Private Instance Methods

entries_by_uid(members) click to toggle source

Internal: Fetch entries by UID.

Returns an Array of Net::LDAP::Entry objects.

# File lib/github/ldap/member_search/recursive.rb, line 133
def entries_by_uid(members)
  filter = members.map { |uid| Net::LDAP::Filter.eq(ldap.uid, uid) }.reduce(:|)
  domains.each_with_object([]) do |domain, entries|
    entries.concat domain.search(filter: filter, attributes: attrs)
  end.compact
end
find_groups_by_dn(dn) click to toggle source

Internal: Search for Groups by DN.

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.

# File lib/github/ldap/member_search/recursive.rb, line 121
def find_groups_by_dn(dn)
  ldap.search \
    base: dn,
    scope: Net::LDAP::SearchScope_BaseObject,
    attributes: attrs,
    filter: ALL_GROUPS_FILTER
end
member_dns(entry) click to toggle source

Internal: Returns an Array of String DNs for `groupOfNames` and `uniqueGroupOfNames` members.

# File lib/github/ldap/member_search/recursive.rb, line 143
def member_dns(entry)
  MEMBERSHIP_NAMES.each_with_object([]) do |attr_name, members|
    members.concat entry[attr_name]
  end
end
member_uids(entry) click to toggle source

Internal: Returns an Array of String UIDs for PosixGroups members.

# File lib/github/ldap/member_search/recursive.rb, line 151
def member_uids(entry)
  entry["memberUid"]
end