Class: GitHub::Ldap::MembershipValidators::Recursive

Inherits:
Base
  • Object
show all
Includes:
Filter
Defined in:
lib/github/ldap/membership_validators/recursive.rb

Overview

Validates membership recursively.

The first step checks whether the entry is a direct member of the given groups. If they are, then we’ve validated membership successfully.

If not, query for all of the groups that have our groups as members, then we check if the entry is a member of any of those.

This is repeated until the entry is found, recursing and requesting groups in bulk each iteration until we hit the maximum depth allowed and have to give up.

This results in a maximum of ‘depth` queries (per domain) to validate membership in a list of groups.

Constant Summary collapse

DEFAULT_MAX_DEPTH =
9
ATTRS =
%w(dn cn)

Constants included from Filter

Filter::ALL_GROUPS_FILTER, Filter::MEMBERSHIP_NAMES

Instance Attribute Summary collapse

Attributes inherited from Base

#groups, #ldap

Instance Method Summary collapse

Methods included from Filter

#all_members_by_uid, #group_contains_filter, #group_filter, #login_filter, #members_of_group, #posix_member_filter, #subgroups_of_group

Constructor Details

#initialize(ldap, groups, options = {}) ⇒ Recursive

Public: Instantiate new search strategy.

  • ldap: GitHub::Ldap object

  • groups: Array of Net::LDAP::Entry group objects

  • options: Hash of options depth: Integer limit of recursion

NOTE: This overrides default behavior to configure ‘depth`.



35
36
37
38
# File 'lib/github/ldap/membership_validators/recursive.rb', line 35

def initialize(ldap, groups, options = {})
  super
  @depth = options[:depth] || DEFAULT_MAX_DEPTH
end

Instance Attribute Details

#depthObject (readonly)

Internal: The maximum depth to search for membership.



25
26
27
# File 'lib/github/ldap/membership_validators/recursive.rb', line 25

def depth
  @depth
end

Instance Method Details

#group_dnsObject

Internal: the group DNs to check against.

Returns an Array of String DNs.



111
112
113
# File 'lib/github/ldap/membership_validators/recursive.rb', line 111

def group_dns
  @group_dns ||= groups.map(&:dn)
end

#member_filter(entry_or_uid, uid = ldap.uid) ⇒ Object

Internal: Construct a filter to find groups this entry is a direct member of.

Overloads the included ‘GitHub::Ldap::Filters#member_filter` method to inject `posixGroup` handling.

Returns a Net::LDAP::Filter object.



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/github/ldap/membership_validators/recursive.rb', line 88

def member_filter(entry_or_uid, uid = ldap.uid)
  filter = super(entry_or_uid)

  if ldap.posix_support_enabled?
    if posix_filter = posix_member_filter(entry_or_uid, uid)
      filter |= posix_filter
    end
  end

  filter
end

#membership_filter(groups) ⇒ Object

Internal: Construct a filter to find groups whose members are the Array of String group DNs passed in.

Returns a String filter.



104
105
106
# File 'lib/github/ldap/membership_validators/recursive.rb', line 104

def membership_filter(groups)
  groups.map { |entry| member_filter(entry, :cn) }.reduce(:|)
end

#perform(entry, depth_override = nil) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/github/ldap/membership_validators/recursive.rb', line 40

def perform(entry, depth_override = nil)
  if depth_override
    warn "DEPRECATION WARNING: Calling Recursive#perform with a second argument is deprecated."
    warn "Usage:"
    warn "  strategy = GitHub::Ldap::MembershipValidators::Recursive.new \\"
    warn "    ldap, depth: 5"
    warn "  strategy#perform(entry)"
  end

  # short circuit validation if there are no groups to check against
  return true if groups.empty?

  domains.each do |domain|
    # find groups entry is an immediate member of
    membership = domain.search(filter: member_filter(entry), attributes: ATTRS)

    # success if any of these groups match the restricted auth groups
    return true if membership.any? { |entry| group_dns.include?(entry.dn) }

    # give up if the entry has no memberships to recurse
    next if membership.empty?

    # recurse to at most `depth`
    (depth_override || depth).times do |n|
      # find groups whose members include membership groups
      membership = domain.search(filter: membership_filter(membership), attributes: ATTRS)

      # success if any of these groups match the restricted auth groups
      return true if membership.any? { |entry| group_dns.include?(entry.dn) }

      # give up if there are no more membersips to recurse
      break if membership.empty?
    end

    # give up on this base if there are no memberships to test
    next if membership.empty?
  end

  false
end