Skip to content

Commit 0dd969d

Browse files
committed
Add WIP recursive member search strategy
1 parent f7448ee commit 0dd969d

File tree

4 files changed

+139
-0
lines changed

4 files changed

+139
-0
lines changed

lib/github/ldap.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Ldap
99
require 'github/ldap/virtual_group'
1010
require 'github/ldap/virtual_attributes'
1111
require 'github/ldap/instrumentation'
12+
require 'github/ldap/members'
1213
require 'github/ldap/membership_validators'
1314

1415
include Instrumentation

lib/github/ldap/members.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
require 'github/ldap/members/recursive'
2+
3+
module GitHub
4+
class Ldap
5+
# Provides various strategies for member lookup.
6+
#
7+
# For example:
8+
#
9+
# group = domain.groups(%w(Engineering)).first
10+
# strategy = GitHub::Ldap::Members::Recursive.new(ldap)
11+
# strategy.perform(group) #=> [#<Net::LDAP::Entry>]
12+
#
13+
module Members
14+
# Internal: Mapping of strategy name to class.
15+
STRATEGIES = {
16+
:recursive => GitHub::Ldap::Members::Recursive
17+
}
18+
end
19+
end
20+
end

lib/github/ldap/members/recursive.rb

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
module GitHub
2+
class Ldap
3+
module Members
4+
# Look up group members recursively.
5+
#
6+
# In this case, we're returning User Net::LDAP::Entry objects, not entries
7+
# for LDAP Groups.
8+
#
9+
# This results in a maximum of `depth` queries (per domain) to look up
10+
# members of a group and its subgroups.
11+
class Recursive
12+
include Filter
13+
14+
DEFAULT_MAX_DEPTH = 9
15+
ATTRS = %w(dn cn)
16+
17+
# Internal: The GitHub::Ldap object to search domains with.
18+
attr_reader :ldap
19+
20+
# Public: Instantiate new search strategy.
21+
#
22+
# - ldap: GitHub::Ldap object
23+
# - options: Hash of options
24+
def initialize(ldap, options = {})
25+
@ldap = ldap
26+
@options = options
27+
end
28+
29+
# Internal: Domains to search through.
30+
#
31+
# Returns an Array of GitHub::Ldap::Domain objects.
32+
def domains
33+
@domains ||= ldap.search_domains.map { |base| ldap.domain(base) }
34+
end
35+
private :domains
36+
37+
# Public: Performs search for group members, including members of
38+
# subgroups recursively.
39+
#
40+
# Returns Array of Net::LDAP::Entry objects.
41+
def perform(group, depth = DEFAULT_MAX_DEPTH)
42+
members = Hash.new
43+
44+
member_dns = group["member"]
45+
46+
domains.each do |domain|
47+
# find members
48+
entries = domain.search(filter: membership_filter(member_dns), attributes: ATTRS)
49+
50+
next if entries.empty?
51+
52+
return entries
53+
end
54+
55+
[]
56+
end
57+
58+
# Internal: Construct a filter to find groups this entry is a direct
59+
# member of.
60+
#
61+
# Overloads the included `GitHub::Ldap::Filters#member_filter` method
62+
# to inject `posixGroup` handling.
63+
#
64+
# Returns a Net::LDAP::Filter object.
65+
def member_filter(entry_or_uid, uid = ldap.uid)
66+
filter = super(entry_or_uid)
67+
68+
if ldap.posix_support_enabled?
69+
if posix_filter = posix_member_filter(entry_or_uid, uid)
70+
filter |= posix_filter
71+
end
72+
end
73+
74+
filter
75+
end
76+
77+
# Internal: Construct a filter to find groups whose members are the
78+
# Array of String group DNs passed in.
79+
#
80+
# Returns a String filter.
81+
def membership_filter(groups)
82+
groups.map { |entry| member_filter(entry, :cn) }.reduce(:|)
83+
end
84+
85+
# Internal: the group DNs to check against.
86+
#
87+
# Returns an Array of String DNs.
88+
def group_dns
89+
@group_dns ||= groups.map(&:dn)
90+
end
91+
end
92+
end
93+
end
94+
end

test/members/recursive_test.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
require_relative '../test_helper'
2+
3+
class GitHubLdapRecursiveMembersTest < GitHub::Ldap::Test
4+
def setup
5+
@ldap = GitHub::Ldap.new(options.merge(search_domains: %w(dc=github,dc=com)))
6+
@domain = @ldap.domain("dc=github,dc=com")
7+
@entry = @domain.user?('user1')
8+
@strategy = GitHub::Ldap::Members::Recursive.new(@ldap)
9+
end
10+
11+
def find_group(cn)
12+
@domain.groups([cn]).first
13+
end
14+
15+
def test_finds_group_members
16+
members = @strategy.perform(find_group("nested-group1")).map(&:dn)
17+
assert_includes members, @entry.dn
18+
end
19+
20+
def test_finds_nested_group_members
21+
members = @strategy.perform(find_group("n-depth-nested-group1")).map(&:dn)
22+
assert_includes members, @entry.dn
23+
end
24+
end

0 commit comments

Comments
 (0)