From 0dd969d6386bfd4f9889d39acb193306a344b6cf Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Mon, 24 Nov 2014 18:29:40 -0800 Subject: [PATCH 01/10] Add WIP recursive member search strategy --- lib/github/ldap.rb | 1 + lib/github/ldap/members.rb | 20 ++++++ lib/github/ldap/members/recursive.rb | 94 ++++++++++++++++++++++++++++ test/members/recursive_test.rb | 24 +++++++ 4 files changed, 139 insertions(+) create mode 100644 lib/github/ldap/members.rb create mode 100644 lib/github/ldap/members/recursive.rb create mode 100644 test/members/recursive_test.rb diff --git a/lib/github/ldap.rb b/lib/github/ldap.rb index 3258ac4..43c3f3b 100644 --- a/lib/github/ldap.rb +++ b/lib/github/ldap.rb @@ -9,6 +9,7 @@ class Ldap require 'github/ldap/virtual_group' require 'github/ldap/virtual_attributes' require 'github/ldap/instrumentation' + require 'github/ldap/members' require 'github/ldap/membership_validators' include Instrumentation diff --git a/lib/github/ldap/members.rb b/lib/github/ldap/members.rb new file mode 100644 index 0000000..65a2539 --- /dev/null +++ b/lib/github/ldap/members.rb @@ -0,0 +1,20 @@ +require 'github/ldap/members/recursive' + +module GitHub + class Ldap + # Provides various strategies for member lookup. + # + # For example: + # + # group = domain.groups(%w(Engineering)).first + # strategy = GitHub::Ldap::Members::Recursive.new(ldap) + # strategy.perform(group) #=> [#] + # + module Members + # Internal: Mapping of strategy name to class. + STRATEGIES = { + :recursive => GitHub::Ldap::Members::Recursive + } + end + end +end diff --git a/lib/github/ldap/members/recursive.rb b/lib/github/ldap/members/recursive.rb new file mode 100644 index 0000000..7de20c9 --- /dev/null +++ b/lib/github/ldap/members/recursive.rb @@ -0,0 +1,94 @@ +module GitHub + class Ldap + module Members + # Look up group members recursively. + # + # In this case, we're returning User Net::LDAP::Entry objects, not entries + # for LDAP Groups. + # + # This results in a maximum of `depth` queries (per domain) to look up + # members of a group and its subgroups. + class Recursive + include Filter + + DEFAULT_MAX_DEPTH = 9 + ATTRS = %w(dn cn) + + # Internal: The GitHub::Ldap object to search domains with. + attr_reader :ldap + + # Public: Instantiate new search strategy. + # + # - ldap: GitHub::Ldap object + # - options: Hash of options + def initialize(ldap, options = {}) + @ldap = ldap + @options = options + end + + # Internal: Domains to search through. + # + # Returns an Array of GitHub::Ldap::Domain objects. + def domains + @domains ||= ldap.search_domains.map { |base| ldap.domain(base) } + end + private :domains + + # Public: Performs search for group members, including members of + # subgroups recursively. + # + # Returns Array of Net::LDAP::Entry objects. + def perform(group, depth = DEFAULT_MAX_DEPTH) + members = Hash.new + + member_dns = group["member"] + + domains.each do |domain| + # find members + entries = domain.search(filter: membership_filter(member_dns), attributes: ATTRS) + + next if entries.empty? + + return entries + end + + [] + end + + # 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. + 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 + + # Internal: Construct a filter to find groups whose members are the + # Array of String group DNs passed in. + # + # Returns a String filter. + def membership_filter(groups) + groups.map { |entry| member_filter(entry, :cn) }.reduce(:|) + end + + # Internal: the group DNs to check against. + # + # Returns an Array of String DNs. + def group_dns + @group_dns ||= groups.map(&:dn) + end + end + end + end +end diff --git a/test/members/recursive_test.rb b/test/members/recursive_test.rb new file mode 100644 index 0000000..ddcf866 --- /dev/null +++ b/test/members/recursive_test.rb @@ -0,0 +1,24 @@ +require_relative '../test_helper' + +class GitHubLdapRecursiveMembersTest < GitHub::Ldap::Test + def setup + @ldap = GitHub::Ldap.new(options.merge(search_domains: %w(dc=github,dc=com))) + @domain = @ldap.domain("dc=github,dc=com") + @entry = @domain.user?('user1') + @strategy = GitHub::Ldap::Members::Recursive.new(@ldap) + end + + def find_group(cn) + @domain.groups([cn]).first + end + + def test_finds_group_members + members = @strategy.perform(find_group("nested-group1")).map(&:dn) + assert_includes members, @entry.dn + end + + def test_finds_nested_group_members + members = @strategy.perform(find_group("n-depth-nested-group1")).map(&:dn) + assert_includes members, @entry.dn + end +end From 3892f4e75011a3ba4b78f3d3cd10816c090f0489 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Mon, 24 Nov 2014 19:47:18 -0800 Subject: [PATCH 02/10] Implement recursive group member search --- lib/github/ldap/domain.rb | 7 ++- lib/github/ldap/members/recursive.rb | 87 +++++++++++++++------------- 2 files changed, 53 insertions(+), 41 deletions(-) diff --git a/lib/github/ldap/domain.rb b/lib/github/ldap/domain.rb index aa2066b..8fd904f 100644 --- a/lib/github/ldap/domain.rb +++ b/lib/github/ldap/domain.rb @@ -163,8 +163,11 @@ def search(options, &block) # Get the entry for this domain. # # Returns a Net::LDAP::Entry - def bind - search(size: 1, scope: Net::LDAP::SearchScope_BaseObject).first + def bind(options = {}) + options[:size] = 1 + options[:scope] = Net::LDAP::SearchScope_BaseObject + options[:attributes] ||= [] + search(options).first end end end diff --git a/lib/github/ldap/members/recursive.rb b/lib/github/ldap/members/recursive.rb index 7de20c9..15183c8 100644 --- a/lib/github/ldap/members/recursive.rb +++ b/lib/github/ldap/members/recursive.rb @@ -12,11 +12,14 @@ class Recursive include Filter DEFAULT_MAX_DEPTH = 9 - ATTRS = %w(dn cn) + ATTRS = %w(dn cn member) # Internal: The GitHub::Ldap object to search domains with. attr_reader :ldap + # Internal: The maximum depth to search for members. + attr_reader :depth + # Public: Instantiate new search strategy. # # - ldap: GitHub::Ldap object @@ -24,6 +27,7 @@ class Recursive def initialize(ldap, options = {}) @ldap = ldap @options = options + @depth = options[:depth] || DEFAULT_MAX_DEPTH end # Internal: Domains to search through. @@ -34,59 +38,64 @@ def domains end private :domains - # Public: Performs search for group members, including members of - # subgroups recursively. + # Public: Performs search for group members, including groups and + # members of subgroups recursively. # # Returns Array of Net::LDAP::Entry objects. - def perform(group, depth = DEFAULT_MAX_DEPTH) - members = Hash.new - - member_dns = group["member"] + def perform(group) + found = Hash.new - domains.each do |domain| - # find members - entries = domain.search(filter: membership_filter(member_dns), attributes: ATTRS) + members = group["member"] + return [] if members.empty? - next if entries.empty? + # find members (N queries) + entries = entries_by_dn(members) + return [] if entries.empty? - return entries + # track found entries + entries.each do |entry| + found[entry.dn] = entry end - [] - 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"] - # 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. - def member_filter(entry_or_uid, uid = ldap.uid) - filter = super(entry_or_uid) + # skip any members we've already found + submembers.reject! { |dn| found.key?(dn) } + + next if submembers.empty? + + # find members of subgroup, including subgroups (N queries) + subentries = entries_by_dn(submembers) - if ldap.posix_support_enabled? - if posix_filter = posix_member_filter(entry_or_uid, uid) - filter |= posix_filter + # track found subentries + subentries.each { |entry| found[entry.dn] = entry } + + # collect all entries for this depth + depth_entries.concat subentries end - end - filter - end + # stop if there are no more subgroups to search + break if depth_subentries.empty? - # Internal: Construct a filter to find groups whose members are the - # Array of String group DNs passed in. - # - # Returns a String filter. - def membership_filter(groups) - groups.map { |entry| member_filter(entry, :cn) }.reduce(:|) + # go one level deeper + entries = depth_subentries + end + + # return all found entries + found.values end - # Internal: the group DNs to check against. + # Internal: Bind a list of DNs to their respective entries. # - # Returns an Array of String DNs. - def group_dns - @group_dns ||= groups.map(&: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 end end end From 19b60bdf84c8c5bdc184d75cd29c86f0ba95d0ad Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Mon, 24 Nov 2014 22:13:05 -0800 Subject: [PATCH 03/10] Clean up unneeded bits --- lib/github/ldap/members/recursive.rb | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/github/ldap/members/recursive.rb b/lib/github/ldap/members/recursive.rb index 15183c8..7e496f4 100644 --- a/lib/github/ldap/members/recursive.rb +++ b/lib/github/ldap/members/recursive.rb @@ -3,16 +3,13 @@ class Ldap module Members # Look up group members recursively. # - # In this case, we're returning User Net::LDAP::Entry objects, not entries - # for LDAP Groups. - # - # This results in a maximum of `depth` queries (per domain) to look up + # This results in a maximum of `depth` iterations/recursions to look up # members of a group and its subgroups. class Recursive include Filter DEFAULT_MAX_DEPTH = 9 - ATTRS = %w(dn cn member) + ATTRS = %w(dn member) # Internal: The GitHub::Ldap object to search domains with. attr_reader :ldap @@ -30,14 +27,6 @@ def initialize(ldap, options = {}) @depth = options[:depth] || DEFAULT_MAX_DEPTH end - # Internal: Domains to search through. - # - # Returns an Array of GitHub::Ldap::Domain objects. - def domains - @domains ||= ldap.search_domains.map { |base| ldap.domain(base) } - end - private :domains - # Public: Performs search for group members, including groups and # members of subgroups recursively. # From 9f48809ca3ae23a766d0589f015e1384bc4a0ccd Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Mon, 24 Nov 2014 22:13:20 -0800 Subject: [PATCH 04/10] Add Classic members search strategy --- lib/github/ldap/members.rb | 2 ++ lib/github/ldap/members/classic.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 lib/github/ldap/members/classic.rb diff --git a/lib/github/ldap/members.rb b/lib/github/ldap/members.rb index 65a2539..85b7f37 100644 --- a/lib/github/ldap/members.rb +++ b/lib/github/ldap/members.rb @@ -1,3 +1,4 @@ +require 'github/ldap/members/classic' require 'github/ldap/members/recursive' module GitHub @@ -13,6 +14,7 @@ class Ldap module Members # Internal: Mapping of strategy name to class. STRATEGIES = { + :classic => GitHub::Ldap::Members::Classic, :recursive => GitHub::Ldap::Members::Recursive } end diff --git a/lib/github/ldap/members/classic.rb b/lib/github/ldap/members/classic.rb new file mode 100644 index 0000000..81a5601 --- /dev/null +++ b/lib/github/ldap/members/classic.rb @@ -0,0 +1,30 @@ +module GitHub + class Ldap + module Members + # Look up group members using the existing `Group#members` and + # `Group#subgroups` API. + class Classic + # Internal: The GitHub::Ldap object to search domains with. + attr_reader :ldap + + # Public: Instantiate new search strategy. + # + # - ldap: GitHub::Ldap object + # - options: Hash of options (unused) + def initialize(ldap, options = {}) + @ldap = ldap + @options = options + end + + # Public: Performs search for group members, including groups and + # members of subgroups recursively. + # + # Returns Array of Net::LDAP::Entry objects. + def perform(group_entry) + group = ldap.load_group(group_entry) + group.members + group.subgroups + end + end + end + end +end From 15b47712784455de4baee7d970e7bb8065e4e586 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 25 Nov 2014 16:13:13 -0800 Subject: [PATCH 05/10] Test deep recursion, configurable depth limiting --- test/members/recursive_test.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/members/recursive_test.rb b/test/members/recursive_test.rb index ddcf866..d34f3bb 100644 --- a/test/members/recursive_test.rb +++ b/test/members/recursive_test.rb @@ -21,4 +21,15 @@ def test_finds_nested_group_members members = @strategy.perform(find_group("n-depth-nested-group1")).map(&:dn) assert_includes members, @entry.dn end + + def test_finds_deeply_nested_group_members + members = @strategy.perform(find_group("n-depth-nested-group9")).map(&:dn) + assert_includes members, @entry.dn + end + + def test_respects_configured_depth_limit + strategy = GitHub::Ldap::Members::Recursive.new(@ldap, depth: 2) + members = strategy.perform(find_group("n-depth-nested-group9")).map(&:dn) + refute_includes members, @entry.dn + end end From 7129554127a752aa18507cf914effc8caee4a6e6 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 25 Nov 2014 16:32:30 -0800 Subject: [PATCH 06/10] Include uniqueMember, memberUid in attrs to fetch --- lib/github/ldap/members/recursive.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/ldap/members/recursive.rb b/lib/github/ldap/members/recursive.rb index 7e496f4..ab915af 100644 --- a/lib/github/ldap/members/recursive.rb +++ b/lib/github/ldap/members/recursive.rb @@ -9,7 +9,7 @@ class Recursive include Filter DEFAULT_MAX_DEPTH = 9 - ATTRS = %w(dn member) + ATTRS = %w(dn member uniqueMember memberUid) # Internal: The GitHub::Ldap object to search domains with. attr_reader :ldap From b01e0ea87fb5a8d84a21989a7ea939f6d69a03c1 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 25 Nov 2014 16:33:46 -0800 Subject: [PATCH 07/10] Refactor member entry search, add memberUid search, test --- lib/github/ldap/members/recursive.rb | 49 ++++++++++++++++++++++++---- test/members/recursive_test.rb | 5 +++ 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/lib/github/ldap/members/recursive.rb b/lib/github/ldap/members/recursive.rb index ab915af..683bec5 100644 --- a/lib/github/ldap/members/recursive.rb +++ b/lib/github/ldap/members/recursive.rb @@ -34,11 +34,8 @@ def initialize(ldap, options = {}) def perform(group) found = Hash.new - members = group["member"] - return [] if members.empty? - # find members (N queries) - entries = entries_by_dn(members) + entries = member_entries(group) return [] if entries.empty? # track found entries @@ -55,10 +52,9 @@ def perform(group) # skip any members we've already found submembers.reject! { |dn| found.key?(dn) } - next if submembers.empty? - # find members of subgroup, including subgroups (N queries) - subentries = entries_by_dn(submembers) + subentries = member_entries(entry) + next if subentries.empty? # track found subentries subentries.each { |entry| found[entry.dn] = entry } @@ -78,6 +74,17 @@ def perform(group) found.values end + # Internal: Fetch member entries, including subgroups, for the given + # entry. + # + # Returns an Array of Net::LDAP::Entry objects. + def member_entries(entry) + dns = member_dns(entry) + return [] if dns.empty? + + entries_by_dn(dns) + end + # Internal: Bind a list of DNs to their respective entries. # # Returns an Array of Net::LDAP::Entry objects. @@ -86,6 +93,34 @@ def entries_by_dn(members) ldap.domain(dn).bind(attributes: ATTRS) end.compact end + + 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 + + # Internal: Returns an Array of String DNs for `groupOfNames` and + # `uniqueGroupOfNames` members. + def member_dns(entry) + MEMBERSHIP_NAMES.each_with_object([]) do |attr_name, members| + members.concat entry[attr_name] + end + end + + # Internal: Returns an Array of String UIDs for PosixGroups members. + def member_uids(entry) + entry["memberUid"] + end + + # Internal: Domains to search through. + # + # Returns an Array of GitHub::Ldap::Domain objects. + def domains + @domains ||= ldap.search_domains.map { |base| ldap.domain(base) } + end + private :domains end end end diff --git a/test/members/recursive_test.rb b/test/members/recursive_test.rb index d34f3bb..e743ca8 100644 --- a/test/members/recursive_test.rb +++ b/test/members/recursive_test.rb @@ -27,6 +27,11 @@ def test_finds_deeply_nested_group_members assert_includes members, @entry.dn end + def test_finds_posix_group_members + members = @strategy.perform(find_group("posix-group1")).map(&:dn) + assert_includes members, @entry.dn + end + def test_respects_configured_depth_limit strategy = GitHub::Ldap::Members::Recursive.new(@ldap, depth: 2) members = strategy.perform(find_group("n-depth-nested-group9")).map(&:dn) From 3822f4bb1739d163354ee6c6a5de5c8c14b4b762 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 25 Nov 2014 16:36:54 -0800 Subject: [PATCH 08/10] Fetch member entries by DN, UID --- lib/github/ldap/members/recursive.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/github/ldap/members/recursive.rb b/lib/github/ldap/members/recursive.rb index 683bec5..2ca10bf 100644 --- a/lib/github/ldap/members/recursive.rb +++ b/lib/github/ldap/members/recursive.rb @@ -79,10 +79,14 @@ def perform(group) # # Returns an Array of Net::LDAP::Entry objects. def member_entries(entry) - dns = member_dns(entry) - return [] if dns.empty? + entries = [] + dns = member_dns(entry) + uids = member_uids(entry) - entries_by_dn(dns) + entries.concat entries_by_uid(uids) unless uids.empty? + entries.concat entries_by_dn(dns) unless dns.empty? + + entries end # Internal: Bind a list of DNs to their respective entries. From 9418bd06ba399392e5f8077520bc9455f0b91bee Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 25 Nov 2014 16:39:11 -0800 Subject: [PATCH 09/10] Doc entries_by_uid method, make internal methods private --- lib/github/ldap/members/recursive.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/github/ldap/members/recursive.rb b/lib/github/ldap/members/recursive.rb index 2ca10bf..0e1bef9 100644 --- a/lib/github/ldap/members/recursive.rb +++ b/lib/github/ldap/members/recursive.rb @@ -88,6 +88,7 @@ def member_entries(entry) entries end + private :member_entries # Internal: Bind a list of DNs to their respective entries. # @@ -97,13 +98,18 @@ def entries_by_dn(members) ldap.domain(dn).bind(attributes: ATTRS) end.compact end + private :entries_by_dn + # Internal: Fetch entries by UID. + # + # Returns an Array of Net::LDAP::Entry objects. 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 + private :entries_by_uid # Internal: Returns an Array of String DNs for `groupOfNames` and # `uniqueGroupOfNames` members. @@ -112,11 +118,13 @@ def member_dns(entry) members.concat entry[attr_name] end end + private :member_dns # Internal: Returns an Array of String UIDs for PosixGroups members. def member_uids(entry) entry["memberUid"] end + private :member_uids # Internal: Domains to search through. # From 76546821dd458009bcbda22024f9e6ab5fa101ca Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 25 Nov 2014 16:41:44 -0800 Subject: [PATCH 10/10] Test classic member search strategy --- test/members/classic_test.rb | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 test/members/classic_test.rb diff --git a/test/members/classic_test.rb b/test/members/classic_test.rb new file mode 100644 index 0000000..6c36a3f --- /dev/null +++ b/test/members/classic_test.rb @@ -0,0 +1,40 @@ +require_relative '../test_helper' + +class GitHubLdapRecursiveMembersTest < GitHub::Ldap::Test + def setup + @ldap = GitHub::Ldap.new(options.merge(search_domains: %w(dc=github,dc=com))) + @domain = @ldap.domain("dc=github,dc=com") + @entry = @domain.user?('user1') + @strategy = GitHub::Ldap::Members::Classic.new(@ldap) + end + + def find_group(cn) + @domain.groups([cn]).first + end + + def test_finds_group_members + members = @strategy.perform(find_group("nested-group1")).map(&:dn) + assert_includes members, @entry.dn + end + + def test_finds_nested_group_members + members = @strategy.perform(find_group("n-depth-nested-group1")).map(&:dn) + assert_includes members, @entry.dn + end + + def test_finds_deeply_nested_group_members + members = @strategy.perform(find_group("n-depth-nested-group9")).map(&:dn) + assert_includes members, @entry.dn + end + + def test_finds_posix_group_members + members = @strategy.perform(find_group("posix-group1")).map(&:dn) + assert_includes members, @entry.dn + end + + def test_does_not_respect_configured_depth_limit + strategy = GitHub::Ldap::Members::Classic.new(@ldap, depth: 2) + members = strategy.perform(find_group("n-depth-nested-group9")).map(&:dn) + assert_includes members, @entry.dn + end +end pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy