From ee14d5b8955cb94b71de26a53965318b1cc124b3 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 17:37:52 -0700 Subject: [PATCH 01/23] Add classic, recursive membership validators --- lib/github/ldap.rb | 1 + lib/github/ldap/membership_validators.rb | 17 +++++ lib/github/ldap/membership_validators/base.rb | 24 +++++++ .../ldap/membership_validators/classic.rb | 26 ++++++++ .../ldap/membership_validators/recursive.rb | 63 +++++++++++++++++++ 5 files changed, 131 insertions(+) create mode 100644 lib/github/ldap/membership_validators.rb create mode 100644 lib/github/ldap/membership_validators/base.rb create mode 100644 lib/github/ldap/membership_validators/classic.rb create mode 100644 lib/github/ldap/membership_validators/recursive.rb diff --git a/lib/github/ldap.rb b/lib/github/ldap.rb index 9844ceb..1484bec 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/membership_validators' include Instrumentation diff --git a/lib/github/ldap/membership_validators.rb b/lib/github/ldap/membership_validators.rb new file mode 100644 index 0000000..dc3bd30 --- /dev/null +++ b/lib/github/ldap/membership_validators.rb @@ -0,0 +1,17 @@ +module GitHub + class Ldap + # x + # + # For example: + # + # validator = GitHub::Ldap::MembershipValidators::Classic.new(ldap, %w(Engineering)) + # validator.perform(entry) #=> true + # + module MembershipValidators + require 'github/ldap/membership_validators/base' + + autoload :Classic, 'github/ldap/membership_validators/classic' + autoload :Recursive, 'github/ldap/membership_validators/recursive' + end + end +end diff --git a/lib/github/ldap/membership_validators/base.rb b/lib/github/ldap/membership_validators/base.rb new file mode 100644 index 0000000..e9ecee5 --- /dev/null +++ b/lib/github/ldap/membership_validators/base.rb @@ -0,0 +1,24 @@ +module GitHub + module Ldap + module MembershipValidators + class Base + attr_reader :ldap, :groups + + def initialize(ldap, groups) + @ldap = ldap + @groups = groups + end + + # Abstract: Performs the membership validation check. + # + # Returns Boolean whether the entry's membership is validated or not. + # def perform(entry) + # end + + def domains + ldap.search_domains + end + end + end + end +end diff --git a/lib/github/ldap/membership_validators/classic.rb b/lib/github/ldap/membership_validators/classic.rb new file mode 100644 index 0000000..f1a626e --- /dev/null +++ b/lib/github/ldap/membership_validators/classic.rb @@ -0,0 +1,26 @@ +module GitHub + module Ldap + module MembershipValidators + # Validates membership using the `Domain#membership` lookup method. + # + # This is a simple wrapper for existing functionality in order to expose + # it consistently with the new approach. + class Classic < Base + def perform(entry) + return true if groups.empty? + + domains.each do |base_name, domain| + membership = domain.membership(entry, groups) + + if !membership.empty? + entry[:groups] = membership + return true + end + end + + false + end + end + end + end +end diff --git a/lib/github/ldap/membership_validators/recursive.rb b/lib/github/ldap/membership_validators/recursive.rb new file mode 100644 index 0000000..204dbfe --- /dev/null +++ b/lib/github/ldap/membership_validators/recursive.rb @@ -0,0 +1,63 @@ +module GitHub + module Ldap + module MembershipValidators + # 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. + class Recursive < Base + include Filter + + DEFAULT_MAX_DEPTH = 9 + + def perform(entry, depth = DEFAULT_MAX_DEPTH) + domains.each do |base_name, domain| + # find groups entry is an immediate member of + membership = domain.search(filter: member_filter(entry), attributes: %w(dn)).map(&:dn) + # success if any of these groups match the restricted auth groups + return true if membership.any?{ |dn| groups.include?(dn) } + + # give up if the entry has no memberships to recurse + next if membership.empty? + + # recurse to at most `depth` + depth.times do |n| + # find groups whose members include membership groups + membership = domain.search(filter: membership_filter(membership), attributes: %w(dn)).map(&:dn) + # success if any of these groups match the restricted auth groups + return true if membership.any?{ |dn| groups.include?(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 + + # Internal: Construct a filter to find groups whose members are the + # Array of String group DNs passed in. + # + # FIXME: Not portable (hardcoded to `member` attribute). + # + # Returns a String filter. + def membership_filter(groups) + "(|%s)" % groups.map{ |dn| "(member=#{dn})" }.join + end + end + end + end +end From 72a543ab59fa9422aad3054169a3f559d1ea2a1c Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 17:56:04 -0700 Subject: [PATCH 02/23] GitHub::Ldap is a class How could I forget. --- lib/github/ldap/membership_validators/classic.rb | 2 +- lib/github/ldap/membership_validators/recursive.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/github/ldap/membership_validators/classic.rb b/lib/github/ldap/membership_validators/classic.rb index f1a626e..b032fe9 100644 --- a/lib/github/ldap/membership_validators/classic.rb +++ b/lib/github/ldap/membership_validators/classic.rb @@ -1,5 +1,5 @@ module GitHub - module Ldap + class Ldap module MembershipValidators # Validates membership using the `Domain#membership` lookup method. # diff --git a/lib/github/ldap/membership_validators/recursive.rb b/lib/github/ldap/membership_validators/recursive.rb index 204dbfe..95f9528 100644 --- a/lib/github/ldap/membership_validators/recursive.rb +++ b/lib/github/ldap/membership_validators/recursive.rb @@ -1,5 +1,5 @@ module GitHub - module Ldap + class Ldap module MembershipValidators # Validates membership recursively. # From c2dcbba3e325f97f082316c60d715f1906e7706d Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 17:57:22 -0700 Subject: [PATCH 03/23] Fix doco for MembershipValidators module --- lib/github/ldap/membership_validators.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators.rb b/lib/github/ldap/membership_validators.rb index dc3bd30..89d1ff9 100644 --- a/lib/github/ldap/membership_validators.rb +++ b/lib/github/ldap/membership_validators.rb @@ -1,6 +1,6 @@ module GitHub class Ldap - # x + # Provides various strategies for validating membership. # # For example: # From f412a055b241f67af815446ab2d89818699ea19f Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 18:23:31 -0700 Subject: [PATCH 04/23] Missed Base --- lib/github/ldap/membership_validators/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/base.rb b/lib/github/ldap/membership_validators/base.rb index e9ecee5..eda4b94 100644 --- a/lib/github/ldap/membership_validators/base.rb +++ b/lib/github/ldap/membership_validators/base.rb @@ -1,5 +1,5 @@ module GitHub - module Ldap + class Ldap module MembershipValidators class Base attr_reader :ldap, :groups From 4017f92f1925080334b75c4c9a7b515091b1709a Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 19:07:44 -0700 Subject: [PATCH 05/23] Significant fixes and :nail_care: --- lib/github/ldap/membership_validators/base.rb | 2 +- lib/github/ldap/membership_validators/classic.rb | 13 +++++++++++-- lib/github/ldap/membership_validators/recursive.rb | 13 ++++++++++--- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/github/ldap/membership_validators/base.rb b/lib/github/ldap/membership_validators/base.rb index eda4b94..96b77b0 100644 --- a/lib/github/ldap/membership_validators/base.rb +++ b/lib/github/ldap/membership_validators/base.rb @@ -16,7 +16,7 @@ def initialize(ldap, groups) # end def domains - ldap.search_domains + @domains ||= ldap.search_domains.map { |base| ldap.domain(base) } end end end diff --git a/lib/github/ldap/membership_validators/classic.rb b/lib/github/ldap/membership_validators/classic.rb index b032fe9..b59a018 100644 --- a/lib/github/ldap/membership_validators/classic.rb +++ b/lib/github/ldap/membership_validators/classic.rb @@ -9,8 +9,8 @@ class Classic < Base def perform(entry) return true if groups.empty? - domains.each do |base_name, domain| - membership = domain.membership(entry, groups) + domains.each do |domain| + membership = domain.membership(entry, group_names) if !membership.empty? entry[:groups] = membership @@ -20,6 +20,15 @@ def perform(entry) false end + + # Internal: the group names to look up membership for. + # + # FIXME: Hardcoded to CN. + # + # Returns an Array of String group names (CNs). + def group_names + @group_names ||= groups.map { |g| g[:cn].first } + end end end end diff --git a/lib/github/ldap/membership_validators/recursive.rb b/lib/github/ldap/membership_validators/recursive.rb index 95f9528..0f2596c 100644 --- a/lib/github/ldap/membership_validators/recursive.rb +++ b/lib/github/ldap/membership_validators/recursive.rb @@ -21,11 +21,11 @@ class Recursive < Base DEFAULT_MAX_DEPTH = 9 def perform(entry, depth = DEFAULT_MAX_DEPTH) - domains.each do |base_name, domain| + domains.each do |domain| # find groups entry is an immediate member of membership = domain.search(filter: member_filter(entry), attributes: %w(dn)).map(&:dn) # success if any of these groups match the restricted auth groups - return true if membership.any?{ |dn| groups.include?(dn) } + return true if membership.any?{ |dn| group_dns.include?(dn) } # give up if the entry has no memberships to recurse next if membership.empty? @@ -35,7 +35,7 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) # find groups whose members include membership groups membership = domain.search(filter: membership_filter(membership), attributes: %w(dn)).map(&:dn) # success if any of these groups match the restricted auth groups - return true if membership.any?{ |dn| groups.include?(dn) } + return true if membership.any?{ |dn| group_dns.include?(dn) } # give up if there are no more membersips to recurse break if membership.empty? @@ -57,6 +57,13 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) def membership_filter(groups) "(|%s)" % groups.map{ |dn| "(member=#{dn})" }.join 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 From 4dbf604b46b692d3b0345d3a45c3db471fa53d07 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 19:08:28 -0700 Subject: [PATCH 06/23] Add rudimentary tests for MembershipValidators --- test/membership_validators_test.rb | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 test/membership_validators_test.rb diff --git a/test/membership_validators_test.rb b/test/membership_validators_test.rb new file mode 100644 index 0000000..f33bee9 --- /dev/null +++ b/test/membership_validators_test.rb @@ -0,0 +1,54 @@ +require_relative 'test_helper' + +module GitHubLdapMembershipValidatorsTestCases + def make_validator(groups) + groups = @domain.groups(groups) + @validator.new(@ldap, groups) + end + + def test_validates_user_in_group + validator = make_validator(%w(Enterprise)) + assert validator.perform(@entry) + end + + def test_does_not_validate_user_not_in_group + validator = make_validator(%w(People)) + refute validator.perform(@entry) + end + + def test_does_not_validate_user_not_in_any_group + @entry = @domain.user?('ldaptest') + validator = make_validator(%w(Enterprise People)) + refute validator.perform(@entry) + end +end + +class GitHubLdapMembershipValidatorsClassicTest < GitHub::Ldap::Test + include GitHubLdapMembershipValidatorsTestCases + + def self.test_server_options + {search_domains: "dc=github,dc=com"} + end + + def setup + @ldap = GitHub::Ldap.new(options) + @domain = @ldap.domain("dc=github,dc=com") + @entry = @domain.user?('calavera') + @validator = GitHub::Ldap::MembershipValidators::Classic + end +end + +class GitHubLdapMembershipValidatorsRecursiveTest < GitHub::Ldap::Test + include GitHubLdapMembershipValidatorsTestCases + + def self.test_server_options + {search_domains: "dc=github,dc=com"} + end + + def setup + @ldap = GitHub::Ldap.new(options) + @domain = @ldap.domain("dc=github,dc=com") + @entry = @domain.user?('calavera') + @validator = GitHub::Ldap::MembershipValidators::Recursive + end +end From 00e8a036918a89bfb83d1f3364c71d46ff6168f0 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 19:32:46 -0700 Subject: [PATCH 07/23] Doco base methods --- lib/github/ldap/membership_validators/base.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/github/ldap/membership_validators/base.rb b/lib/github/ldap/membership_validators/base.rb index 96b77b0..a625555 100644 --- a/lib/github/ldap/membership_validators/base.rb +++ b/lib/github/ldap/membership_validators/base.rb @@ -4,6 +4,10 @@ module MembershipValidators class Base attr_reader :ldap, :groups + # Public: Instantiate new validator. + # + # - ldap: GitHub::Ldap object + # - groups: Array of Net::LDAP::Entry group objects def initialize(ldap, groups) @ldap = ldap @groups = groups @@ -15,6 +19,9 @@ def initialize(ldap, groups) # def perform(entry) # 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 From 8d87584e4074d904e19c67b1350f86f8603ee341 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 20:06:18 -0700 Subject: [PATCH 08/23] CNs are hardcoded elsewhere --- lib/github/ldap/membership_validators/classic.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/github/ldap/membership_validators/classic.rb b/lib/github/ldap/membership_validators/classic.rb index b59a018..e46bbae 100644 --- a/lib/github/ldap/membership_validators/classic.rb +++ b/lib/github/ldap/membership_validators/classic.rb @@ -23,8 +23,6 @@ def perform(entry) # Internal: the group names to look up membership for. # - # FIXME: Hardcoded to CN. - # # Returns an Array of String group names (CNs). def group_names @group_names ||= groups.map { |g| g[:cn].first } From 65dbcb9fea28c8d724ec5d6cabaf9fadb4989936 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 20:28:21 -0700 Subject: [PATCH 09/23] Accept String DN for member_filter --- lib/github/ldap/filter.rb | 8 +++++--- test/filter_test.rb | 5 +++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/github/ldap/filter.rb b/lib/github/ldap/filter.rb index a238642..c25bef2 100644 --- a/lib/github/ldap/filter.rb +++ b/lib/github/ldap/filter.rb @@ -20,16 +20,18 @@ def group_filter(group_names) # Filter to check group membership. # - # entry: finds groups this Net::LDAP::Entry is a member of (optional) + # entry: finds groups this entry is a member of (optional) + # Expects a Net::LDAP::Entry or String DN. # # Returns a Net::LDAP::Filter. def member_filter(entry = nil) if entry + entry = entry.dn if entry.respond_to?(:dn) MEMBERSHIP_NAMES. - map {|n| Net::LDAP::Filter.eq(n, entry.dn) }.reduce(:|) + map {|n| Net::LDAP::Filter.eq(n, entry) }.reduce(:|) else MEMBERSHIP_NAMES. - map {|n| Net::LDAP::Filter.pres(n) }. reduce(:|) + map {|n| Net::LDAP::Filter.pres(n) }. reduce(:|) end end diff --git a/test/filter_test.rb b/test/filter_test.rb index 8fc6ba2..47a45e4 100644 --- a/test/filter_test.rb +++ b/test/filter_test.rb @@ -32,6 +32,11 @@ def test_member_equal @subject.member_filter(@entry).to_s end + def test_member_equal_with_string + assert_equal "(|(member=#{@me})(uniqueMember=#{@me}))", + @subject.member_filter(@entry.dn).to_s + end + def test_posix_member_without_uid @entry.uid = nil assert_nil @subject.posix_member_filter(@entry, @ldap.uid) From 97ba49b0000b3fbfcc789ed39bd2567480e2be09 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 20:34:06 -0700 Subject: [PATCH 10/23] Use member_filter for Recursive group search --- lib/github/ldap/membership_validators/recursive.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/recursive.rb b/lib/github/ldap/membership_validators/recursive.rb index 0f2596c..1e7cdf3 100644 --- a/lib/github/ldap/membership_validators/recursive.rb +++ b/lib/github/ldap/membership_validators/recursive.rb @@ -55,7 +55,7 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) # # Returns a String filter. def membership_filter(groups) - "(|%s)" % groups.map{ |dn| "(member=#{dn})" }.join + groups.map{ |dn| member_filter(dn) }.reduce(:|) end # Internal: the group DNs to check against. From 90fb34ad598059243e55914ac118f4853dc7043a Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 20:34:27 -0700 Subject: [PATCH 11/23] Remove FIXME --- lib/github/ldap/membership_validators/recursive.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/github/ldap/membership_validators/recursive.rb b/lib/github/ldap/membership_validators/recursive.rb index 1e7cdf3..3ed2ed6 100644 --- a/lib/github/ldap/membership_validators/recursive.rb +++ b/lib/github/ldap/membership_validators/recursive.rb @@ -51,8 +51,6 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) # Internal: Construct a filter to find groups whose members are the # Array of String group DNs passed in. # - # FIXME: Not portable (hardcoded to `member` attribute). - # # Returns a String filter. def membership_filter(groups) groups.map{ |dn| member_filter(dn) }.reduce(:|) From a1d9d791ccfbc4bb478df37670e844592b3c9d5e Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Tue, 23 Sep 2014 20:56:18 -0700 Subject: [PATCH 12/23] Extract attributes --- lib/github/ldap/membership_validators/recursive.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/github/ldap/membership_validators/recursive.rb b/lib/github/ldap/membership_validators/recursive.rb index 3ed2ed6..6405960 100644 --- a/lib/github/ldap/membership_validators/recursive.rb +++ b/lib/github/ldap/membership_validators/recursive.rb @@ -19,11 +19,13 @@ class Recursive < Base include Filter DEFAULT_MAX_DEPTH = 9 + ATTRS = %w(dn) def perform(entry, depth = DEFAULT_MAX_DEPTH) domains.each do |domain| # find groups entry is an immediate member of - membership = domain.search(filter: member_filter(entry), attributes: %w(dn)).map(&:dn) + membership = domain.search(filter: member_filter(entry), attributes: ATTRS).map(&:dn) + # success if any of these groups match the restricted auth groups return true if membership.any?{ |dn| group_dns.include?(dn) } @@ -33,7 +35,8 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) # recurse to at most `depth` depth.times do |n| # find groups whose members include membership groups - membership = domain.search(filter: membership_filter(membership), attributes: %w(dn)).map(&:dn) + membership = domain.search(filter: membership_filter(membership), attributes: ATTRS).map(&:dn) + # success if any of these groups match the restricted auth groups return true if membership.any?{ |dn| group_dns.include?(dn) } From 7f8bf868901a681d98b7b51fc7bbd144f2ba7116 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Thu, 25 Sep 2014 18:05:39 -0700 Subject: [PATCH 13/23] Fix subgroup fixture CNs Didn't match DN so resulted in false positives. --- test/fixtures/github-with-subgroups.ldif | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/fixtures/github-with-subgroups.ldif b/test/fixtures/github-with-subgroups.ldif index 00dc929..c432edc 100644 --- a/test/fixtures/github-with-subgroups.ldif +++ b/test/fixtures/github-with-subgroups.ldif @@ -50,19 +50,19 @@ member: uid=user1,ou=users,dc=github,dc=com member: cn=group1.1,ou=groups,dc=github,dc=com dn: cn=group1.1,ou=groups,dc=github,dc=com -cn: group1 +cn: group1.1 objectClass: groupOfNames member: uid=user1.1,ou=users,dc=github,dc=com member: cn=group1.1.1,ou=groups,dc=github,dc=com dn: cn=group1.1.1,ou=groups,dc=github,dc=com -cn: group1 +cn: group1.1.1 objectClass: groupOfNames member: uid=user1.1.1,ou=users,dc=github,dc=com member: cn=group1.1.1.1,ou=groups,dc=github,dc=com dn: cn=group1.1.1.1,ou=groups,dc=github,dc=com -cn: group1 +cn: group1.1.1.1 objectClass: groupOfNames member: uid=user1.1.1.1,ou=users,dc=github,dc=com From 8bb15865f2b069a5668df2f98e0c67181bc70793 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Thu, 25 Sep 2014 18:06:32 -0700 Subject: [PATCH 14/23] Add recursion tests for classic, recursive strategies --- test/membership_validators/classic_test.rb | 52 ++++++++++++++++++ test/membership_validators/recursive_test.rb | 57 ++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 test/membership_validators/classic_test.rb create mode 100644 test/membership_validators/recursive_test.rb diff --git a/test/membership_validators/classic_test.rb b/test/membership_validators/classic_test.rb new file mode 100644 index 0000000..1db60ba --- /dev/null +++ b/test/membership_validators/classic_test.rb @@ -0,0 +1,52 @@ +require_relative '../test_helper' + +class GitHubLdapClassicMembershipValidatorsTest < GitHub::Ldap::Test + def self.test_server_options + { search_domains: "dc=github,dc=com", + user_fixtures: FIXTURES.join('github-with-subgroups.ldif').to_s + } + end + + def setup + @ldap = GitHub::Ldap.new(options) + @domain = @ldap.domain("dc=github,dc=com") + @entry = @domain.user?('user1.1.1.1') + @validator = GitHub::Ldap::MembershipValidators::Classic + end + + def make_validator(groups) + groups = @domain.groups(groups) + @validator.new(@ldap, groups) + end + + def test_validates_user_in_group + validator = make_validator(%w(group1.1.1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_child_group + validator = make_validator(%w(group1.1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_grandchild_group + validator = make_validator(%w(group1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_great_grandchild_group + validator = make_validator(%w(group1)) + assert validator.perform(@entry) + end + + def test_does_not_validate_user_not_in_group + validator = make_validator(%w(Enterprise)) + refute validator.perform(@entry) + end + + def test_does_not_validate_user_not_in_any_group + @entry = @domain.user?('admin') + validator = make_validator(%w(Enterprise)) + refute validator.perform(@entry) + end +end diff --git a/test/membership_validators/recursive_test.rb b/test/membership_validators/recursive_test.rb new file mode 100644 index 0000000..183cbf0 --- /dev/null +++ b/test/membership_validators/recursive_test.rb @@ -0,0 +1,57 @@ +require_relative '../test_helper' + +class GitHubLdapRecursiveMembershipValidatorsTest < GitHub::Ldap::Test + def self.test_server_options + { search_domains: "dc=github,dc=com", + user_fixtures: FIXTURES.join('github-with-subgroups.ldif').to_s + } + end + + def setup + @ldap = GitHub::Ldap.new(options) + @domain = @ldap.domain("dc=github,dc=com") + @entry = @domain.user?('user1.1.1.1') + @validator = GitHub::Ldap::MembershipValidators::Recursive + end + + def make_validator(groups) + groups = @domain.groups(groups) + @validator.new(@ldap, groups) + end + + def test_validates_user_in_group + validator = make_validator(%w(group1.1.1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_child_group + validator = make_validator(%w(group1.1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_grandchild_group + validator = make_validator(%w(group1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_great_grandchild_group + validator = make_validator(%w(group1)) + assert validator.perform(@entry) + end + + def test_does_not_validate_user_in_great_granchild_group_with_depth + validator = make_validator(%w(group1)) + refute validator.perform(@entry, 2) + end + + def test_does_not_validate_user_not_in_group + validator = make_validator(%w(Enterprise)) + refute validator.perform(@entry) + end + + def test_does_not_validate_user_not_in_any_group + @entry = @domain.user?('admin') + validator = make_validator(%w(Enterprise)) + refute validator.perform(@entry) + end +end From e97af8d4af9107205e28d4159365c1d583def625 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 14:32:21 -0700 Subject: [PATCH 15/23] Autoload MembershipValidators::Base too h/t @jch --- lib/github/ldap/membership_validators.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/github/ldap/membership_validators.rb b/lib/github/ldap/membership_validators.rb index 89d1ff9..6a3deef 100644 --- a/lib/github/ldap/membership_validators.rb +++ b/lib/github/ldap/membership_validators.rb @@ -8,8 +8,7 @@ class Ldap # validator.perform(entry) #=> true # module MembershipValidators - require 'github/ldap/membership_validators/base' - + autoload :Base, 'github/ldap/membership_validators/base' autoload :Classic, 'github/ldap/membership_validators/classic' autoload :Recursive, 'github/ldap/membership_validators/recursive' end From 26d6bfd1b04f580f94df55ee5866a4aa414681a0 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 14:32:47 -0700 Subject: [PATCH 16/23] Tweak docs --- lib/github/ldap/membership_validators.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators.rb b/lib/github/ldap/membership_validators.rb index 6a3deef..9e3b49d 100644 --- a/lib/github/ldap/membership_validators.rb +++ b/lib/github/ldap/membership_validators.rb @@ -4,7 +4,8 @@ class Ldap # # For example: # - # validator = GitHub::Ldap::MembershipValidators::Classic.new(ldap, %w(Engineering)) + # groups = domain.groups(%w(Engineering)) + # validator = GitHub::Ldap::MembershipValidators::Classic.new(ldap, groups) # validator.perform(entry) #=> true # module MembershipValidators From 3efbdc15f37259e3fcd98ae2441b82043f078dbc Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 14:36:00 -0700 Subject: [PATCH 17/23] Document attrs, make internal method private --- lib/github/ldap/membership_validators/base.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/base.rb b/lib/github/ldap/membership_validators/base.rb index a625555..3c47853 100644 --- a/lib/github/ldap/membership_validators/base.rb +++ b/lib/github/ldap/membership_validators/base.rb @@ -2,7 +2,12 @@ module GitHub class Ldap module MembershipValidators class Base - attr_reader :ldap, :groups + + # Internal: The GitHub::Ldap object to search domains with. + attr_reader :ldap + + # Internal: an Array of Net::LDAP::Entry group objects to validate with. + attr_reader :groups # Public: Instantiate new validator. # @@ -25,6 +30,7 @@ def initialize(ldap, groups) def domains @domains ||= ldap.search_domains.map { |base| ldap.domain(base) } end + private :domains end end end From 0b98dc5cbedf69467053444de1c0470dbf183ca5 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 14:37:16 -0700 Subject: [PATCH 18/23] Refer to full class name in docs --- lib/github/ldap/membership_validators/classic.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/classic.rb b/lib/github/ldap/membership_validators/classic.rb index e46bbae..f1cda3e 100644 --- a/lib/github/ldap/membership_validators/classic.rb +++ b/lib/github/ldap/membership_validators/classic.rb @@ -1,7 +1,7 @@ module GitHub class Ldap module MembershipValidators - # Validates membership using the `Domain#membership` lookup method. + # Validates membership using `GitHub::Ldap::Domain#membership`. # # This is a simple wrapper for existing functionality in order to expose # it consistently with the new approach. From 76cefb895c8b1b8739bdf48979056d48e791aa22 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 14:38:47 -0700 Subject: [PATCH 19/23] Give these blocks some breathing room --- lib/github/ldap/membership_validators/recursive.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/github/ldap/membership_validators/recursive.rb b/lib/github/ldap/membership_validators/recursive.rb index 6405960..65df2f3 100644 --- a/lib/github/ldap/membership_validators/recursive.rb +++ b/lib/github/ldap/membership_validators/recursive.rb @@ -27,7 +27,7 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) membership = domain.search(filter: member_filter(entry), attributes: ATTRS).map(&:dn) # success if any of these groups match the restricted auth groups - return true if membership.any?{ |dn| group_dns.include?(dn) } + return true if membership.any? { |dn| group_dns.include?(dn) } # give up if the entry has no memberships to recurse next if membership.empty? @@ -38,7 +38,7 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) membership = domain.search(filter: membership_filter(membership), attributes: ATTRS).map(&:dn) # success if any of these groups match the restricted auth groups - return true if membership.any?{ |dn| group_dns.include?(dn) } + return true if membership.any? { |dn| group_dns.include?(dn) } # give up if there are no more membersips to recurse break if membership.empty? @@ -56,7 +56,7 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) # # Returns a String filter. def membership_filter(groups) - groups.map{ |dn| member_filter(dn) }.reduce(:|) + groups.map { |dn| member_filter(dn) }.reduce(:|) end # Internal: the group DNs to check against. From d31baf56898b2771be3b97f8a26d97b5a8cc6ed8 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 15:35:24 -0700 Subject: [PATCH 20/23] Accept UID String for posix_member_filter --- lib/github/ldap/filter.rb | 14 ++++++++++---- test/filter_test.rb | 8 +++++++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/github/ldap/filter.rb b/lib/github/ldap/filter.rb index c25bef2..64f5aa3 100644 --- a/lib/github/ldap/filter.rb +++ b/lib/github/ldap/filter.rb @@ -43,10 +43,16 @@ def member_filter(entry = nil) # uid_attr: specifies the memberUid attribute to match with # # Returns a Net::LDAP::Filter or nil if no entry has no UID set. - def posix_member_filter(entry, uid_attr) - if !entry[uid_attr].empty? - entry[uid_attr].map { |uid| Net::LDAP::Filter.eq("memberUid", uid) }. - reduce(:|) + def posix_member_filter(entry_or_uid, uid_attr = nil) + case entry_or_uid + when Net::LDAP::Entry + entry = entry_or_uid + if !entry[uid_attr].empty? + entry[uid_attr].map { |uid| Net::LDAP::Filter.eq("memberUid", uid) }. + reduce(:|) + end + when String + Net::LDAP::Filter.eq("memberUid", entry_or_uid) end end diff --git a/test/filter_test.rb b/test/filter_test.rb index 47a45e4..58992a8 100644 --- a/test/filter_test.rb +++ b/test/filter_test.rb @@ -20,7 +20,8 @@ def setup @subject = Subject.new(@ldap) @me = 'uid=calavera,dc=github,dc=com' @uid = "calavera" - @entry = Entry.new(@me, @uid) + @entry = Net::LDAP::Entry.new(@me) + @entry[:uid] = @uid end def test_member_present @@ -47,6 +48,11 @@ def test_posix_member_equal @subject.posix_member_filter(@entry, @ldap.uid).to_s end + def test_posix_member_equal_string + assert_equal "(memberUid=#{@uid})", + @subject.posix_member_filter(@uid).to_s + end + def test_groups_reduced assert_equal "(|(cn=Enterprise)(cn=People))", @subject.group_filter(%w(Enterprise People)).to_s From 45bf499a6335909f09f0ba797a8d186efd820bee Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 16:52:45 -0700 Subject: [PATCH 21/23] Handle posixGroup memberships --- .../ldap/membership_validators/recursive.rb | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/github/ldap/membership_validators/recursive.rb b/lib/github/ldap/membership_validators/recursive.rb index 65df2f3..7d4936c 100644 --- a/lib/github/ldap/membership_validators/recursive.rb +++ b/lib/github/ldap/membership_validators/recursive.rb @@ -19,15 +19,15 @@ class Recursive < Base include Filter DEFAULT_MAX_DEPTH = 9 - ATTRS = %w(dn) + ATTRS = %w(dn cn) def perform(entry, depth = DEFAULT_MAX_DEPTH) domains.each do |domain| # find groups entry is an immediate member of - membership = domain.search(filter: member_filter(entry), attributes: ATTRS).map(&:dn) + 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? { |dn| group_dns.include?(dn) } + 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? @@ -35,10 +35,10 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) # recurse to at most `depth` depth.times do |n| # find groups whose members include membership groups - membership = domain.search(filter: membership_filter(membership), attributes: ATTRS).map(&:dn) + 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? { |dn| group_dns.include?(dn) } + 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? @@ -51,12 +51,31 @@ def perform(entry, depth = DEFAULT_MAX_DEPTH) false 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 { |dn| member_filter(dn) }.reduce(:|) + groups.map { |entry| member_filter(entry, :cn) }.reduce(:|) end # Internal: the group DNs to check against. From 5d7d6f426b68b3a3bb1c5cc3c0cac17ea3e34d23 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 16:53:06 -0700 Subject: [PATCH 22/23] Add deep nested posixGroup membership fixtures --- test/fixtures/github-with-posixGroups.ldif | 55 ++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/test/fixtures/github-with-posixGroups.ldif b/test/fixtures/github-with-posixGroups.ldif index ac8b3a0..5269784 100644 --- a/test/fixtures/github-with-posixGroups.ldif +++ b/test/fixtures/github-with-posixGroups.ldif @@ -27,6 +27,29 @@ objectClass: posixGroup memberUid: benburkert memberUid: mtodd +dn: cn=group1,ou=groups,dc=github,dc=com +cn: group1 +objectClass: posixGroup +memberUid: group1.1 +memberUid: user1 + +dn: cn=group1.1,ou=groups,dc=github,dc=com +cn: group1.1 +objectClass: posixGroup +memberUid: group1.1.1 +memberUid: user1.1 + +dn: cn=group1.1.1,ou=groups,dc=github,dc=com +cn: group1.1.1 +objectClass: posixGroup +memberUid: group1.1.1.1 +memberUid: user1.1.1 + +dn: cn=group1.1.1.1,ou=groups,dc=github,dc=com +cn: group1.1.1.1 +objectClass: posixGroup +memberUid: user1.1.1.1 + # Users dn: ou=users,dc=github,dc=com @@ -48,3 +71,35 @@ uid: mtodd userPassword: passworD1 mail: mtodd@github.com objectClass: inetOrgPerson + +dn: uid=user1,ou=users,dc=github,dc=com +cn: user1 +sn: user1 +uid: user1 +userPassword: passworD1 +mail: user1@github.com +objectClass: inetOrgPerson + +dn: uid=user1.1,ou=users,dc=github,dc=com +cn: user1.1 +sn: user1.1 +uid: user1.1 +userPassword: passworD1 +mail: user1.1@github.com +objectClass: inetOrgPerson + +dn: uid=user1.1.1,ou=users,dc=github,dc=com +cn: user1.1.1 +sn: user1.1.1 +uid: user1.1.1 +userPassword: passworD1 +mail: user1.1.1@github.com +objectClass: inetOrgPerson + +dn: uid=user1.1.1.1,ou=users,dc=github,dc=com +cn: user1.1.1.1 +sn: user1.1.1.1 +uid: user1.1.1.1 +userPassword: passworD1 +mail: user1.1.1.1@github.com +objectClass: inetOrgPerson From 084cdf23986d0b7b359fbc490d4724fd9f8c2639 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 26 Sep 2014 16:55:22 -0700 Subject: [PATCH 23/23] Add tests for recursive posixGroup membership validation --- test/membership_validators/classic_test.rb | 27 +++++++++++ test/membership_validators/recursive_test.rb | 48 ++++++++++++++++++++ test/membership_validators_test.rb | 8 +++- 3 files changed, 81 insertions(+), 2 deletions(-) diff --git a/test/membership_validators/classic_test.rb b/test/membership_validators/classic_test.rb index 1db60ba..7802826 100644 --- a/test/membership_validators/classic_test.rb +++ b/test/membership_validators/classic_test.rb @@ -50,3 +50,30 @@ def test_does_not_validate_user_not_in_any_group refute validator.perform(@entry) end end + +class GitHubLdapClassicMembershipValidatorsWithPosixGroupsTest < GitHub::Ldap::Test + def self.test_server_options + { search_domains: "dc=github,dc=com", + uid: "uid", + custom_schemas: FIXTURES.join('posixGroup.schema.ldif'), + user_fixtures: FIXTURES.join('github-with-posixGroups.ldif').to_s + } + end + + def setup + @ldap = GitHub::Ldap.new(options) + @domain = @ldap.domain("dc=github,dc=com") + @entry = @domain.user?('user1.1.1.1') + @validator = GitHub::Ldap::MembershipValidators::Classic + end + + def make_validator(groups) + groups = @domain.groups(groups) + @validator.new(@ldap, groups) + end + + def test_validates_user_in_group + validator = make_validator(%w(group1.1.1.1)) + assert validator.perform(@entry) + end +end diff --git a/test/membership_validators/recursive_test.rb b/test/membership_validators/recursive_test.rb index 183cbf0..fd8e7ee 100644 --- a/test/membership_validators/recursive_test.rb +++ b/test/membership_validators/recursive_test.rb @@ -3,6 +3,7 @@ class GitHubLdapRecursiveMembershipValidatorsTest < GitHub::Ldap::Test def self.test_server_options { search_domains: "dc=github,dc=com", + uid: "uid", user_fixtures: FIXTURES.join('github-with-subgroups.ldif').to_s } end @@ -55,3 +56,50 @@ def test_does_not_validate_user_not_in_any_group refute validator.perform(@entry) end end + +class GitHubLdapRecursiveMembershipValidatorsWithPosixGroupsTest < GitHub::Ldap::Test + def self.test_server_options + { search_domains: "dc=github,dc=com", + uid: "uid", + custom_schemas: FIXTURES.join('posixGroup.schema.ldif'), + user_fixtures: FIXTURES.join('github-with-posixGroups.ldif').to_s + } + end + + def setup + @ldap = GitHub::Ldap.new(options) + @domain = @ldap.domain("dc=github,dc=com") + @entry = @domain.user?('user1.1.1.1') + @validator = GitHub::Ldap::MembershipValidators::Recursive + end + + def make_validator(groups) + groups = @domain.groups(groups) + @validator.new(@ldap, groups) + end + + def test_validates_user_in_group + validator = make_validator(%w(group1.1.1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_child_group + validator = make_validator(%w(group1.1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_grandchild_group + validator = make_validator(%w(group1.1)) + assert validator.perform(@entry) + end + + def test_validates_user_in_great_grandchild_group + validator = make_validator(%w(group1)) + assert validator.perform(@entry) + end + + def test_does_not_validate_user_in_great_granchild_group_with_depth + validator = make_validator(%w(group1)) + refute validator.perform(@entry, 2) + end +end diff --git a/test/membership_validators_test.rb b/test/membership_validators_test.rb index f33bee9..77f0358 100644 --- a/test/membership_validators_test.rb +++ b/test/membership_validators_test.rb @@ -27,7 +27,9 @@ class GitHubLdapMembershipValidatorsClassicTest < GitHub::Ldap::Test include GitHubLdapMembershipValidatorsTestCases def self.test_server_options - {search_domains: "dc=github,dc=com"} + { search_domains: "dc=github,dc=com", + uid: "uid" + } end def setup @@ -42,7 +44,9 @@ class GitHubLdapMembershipValidatorsRecursiveTest < GitHub::Ldap::Test include GitHubLdapMembershipValidatorsTestCases def self.test_server_options - {search_domains: "dc=github,dc=com"} + { search_domains: "dc=github,dc=com", + uid: "uid" + } end def setup 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