From cd3934ec79f2d13d76c095ec769045e5e74687f0 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 14:11:42 -0700 Subject: [PATCH 01/15] Add membership_validator config option, default to detect --- lib/github/ldap.rb | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/github/ldap.rb b/lib/github/ldap.rb index 2edf08a..19d241a 100644 --- a/lib/github/ldap.rb +++ b/lib/github/ldap.rb @@ -34,6 +34,7 @@ class Ldap def_delegator :@connection, :open attr_reader :uid, :search_domains, :virtual_attributes, + :membership_validator, :instrumentation_service # Build a new GitHub::Ldap instance @@ -87,6 +88,9 @@ def initialize(options = {}) # when a base is not explicitly provided. @search_domains = Array(options[:search_domains]) + # configure which strategy should be used to validate user membership + configure_membership_validation_strategy(options[:membership_validator]) + # enables instrumenting queries @instrumentation_service = options[:instrumentation_service] end @@ -214,5 +218,24 @@ def configure_virtual_attributes(attributes) VirtualAttributes.new(false) end end + + # Internal: Configure the membership validation strategy. + # + # Used by GitHub::Ldap::MembershipValidators::Detect to force a specific + # strategy (instead of detecting host capabilities and deciding at runtime). + # + # If `strategy` is not provided, or doesn't match a known strategy, + # defaults to `:detect`. Otherwise the configured strategy is selected. + # + # Returns the selected membership validator strategy Symbol. + def configure_membership_validation_strategy(strategy = nil) + @membership_validator = + case strategy.to_s + when "classic", "recursive", "active_directory" + strategy.to_sym + else + :detect + end + end end end From 22d7995f40ffdba9dded76fb174b2ab8f7878b3d Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 14:13:03 -0700 Subject: [PATCH 02/15] Add Detect membership validation strategy Detects ActiveDirectory capabilities, defaults to Recursive. --- lib/github/ldap/membership_validators.rb | 1 + .../ldap/membership_validators/detect.rb | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 lib/github/ldap/membership_validators/detect.rb diff --git a/lib/github/ldap/membership_validators.rb b/lib/github/ldap/membership_validators.rb index ec3f7b9..8bba69f 100644 --- a/lib/github/ldap/membership_validators.rb +++ b/lib/github/ldap/membership_validators.rb @@ -1,4 +1,5 @@ require 'github/ldap/membership_validators/base' +require 'github/ldap/membership_validators/detect' require 'github/ldap/membership_validators/classic' require 'github/ldap/membership_validators/recursive' require 'github/ldap/membership_validators/active_directory' diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb new file mode 100644 index 0000000..13d202b --- /dev/null +++ b/lib/github/ldap/membership_validators/detect.rb @@ -0,0 +1,73 @@ +module GitHub + class Ldap + module MembershipValidators + # Detects the LDAP host's capabilities and determines the appropriate + # membership validation strategy at runtime. + class Detect < Base + # Internal: Mapping of strategy name to class. + STRATEGIES = { + :classic => GitHub::Ldap::MembershipValidators::Classic, + :recursive => GitHub::Ldap::MembershipValidators::Recursive, + :active_directory => GitHub::Ldap::MembershipValidators::ActiveDirectory + } + + # Internal: The capability required to use the ActiveDirectory strategy. + # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx. + ACTIVE_DIRECTORY_V51_OID = "1.2.840.113556.1.4.1670".freeze + + def perform(entry) + # short circuit validation if there are no groups to check against + return true if groups.empty? + + strategy.perform(entry) + end + + # Internal: Returns the membership validation strategy object. + def strategy + @strategy ||= begin + strategy = detect_strategy + strategy.new(ldap, groups) + end + end + + # Internal: Detects LDAP host's capabilities and chooses the best + # strategy for the host. + # + # If the strategy has been + # + # Returns the strategy class. + def detect_strategy + return STRATEGIES[strategy_config] if STRATEGIES.key?(strategy_config) + + if active_directory_capability? + :active_directory + else + :recursive + end + end + + # Internal: Returns the configured membership validator strategy Symbol. + def strategy_config + ldap.membership_validator + end + + # Internal: Detect it the LDAP host is an ActiveDirectory server. + # + # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx. + # + # Returns true if the host is an ActiveDirectory server, false otherwise. + def active_directory_capability? + capabilities[:supportcapabilities].include?(ACTIVE_DIRECTORY_V51_OID) + end + + # Internal: Searches the LDAP host's Root DSE for server capabilities. + # + # Returns the Net::LDAP::Entry object containing the Root DSE + # results describing the server capabilities. + def capabilities + @capabilities ||= ldap.search_root_dse + end + end + end + end +end From 838c8b1c8d8c3c85c291929a7846c92a0e83f893 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 14:15:18 -0700 Subject: [PATCH 03/15] Move strategies map to GitHub::Ldap::MembershipValidators --- lib/github/ldap/membership_validators.rb | 9 ++++++++- lib/github/ldap/membership_validators/detect.rb | 14 ++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/github/ldap/membership_validators.rb b/lib/github/ldap/membership_validators.rb index 8bba69f..1135a2d 100644 --- a/lib/github/ldap/membership_validators.rb +++ b/lib/github/ldap/membership_validators.rb @@ -14,6 +14,13 @@ class Ldap # validator = GitHub::Ldap::MembershipValidators::Classic.new(ldap, groups) # validator.perform(entry) #=> true # - module MembershipValidators; end + module MembershipValidators + # Internal: Mapping of strategy name to class. + STRATEGIES = { + :classic => GitHub::Ldap::MembershipValidators::Classic, + :recursive => GitHub::Ldap::MembershipValidators::Recursive, + :active_directory => GitHub::Ldap::MembershipValidators::ActiveDirectory + } + end end end diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index 13d202b..2ade0e7 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -4,13 +4,6 @@ module MembershipValidators # Detects the LDAP host's capabilities and determines the appropriate # membership validation strategy at runtime. class Detect < Base - # Internal: Mapping of strategy name to class. - STRATEGIES = { - :classic => GitHub::Ldap::MembershipValidators::Classic, - :recursive => GitHub::Ldap::MembershipValidators::Recursive, - :active_directory => GitHub::Ldap::MembershipValidators::ActiveDirectory - } - # Internal: The capability required to use the ActiveDirectory strategy. # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx. ACTIVE_DIRECTORY_V51_OID = "1.2.840.113556.1.4.1670".freeze @@ -37,9 +30,10 @@ def strategy # # Returns the strategy class. def detect_strategy - return STRATEGIES[strategy_config] if STRATEGIES.key?(strategy_config) - - if active_directory_capability? + case + when GitHub::Ldap::MembershipValidators::STRATEGIES.key?(strategy_config) + GitHub::Ldap::MembershipValidators::STRATEGIES[strategy_config] + when active_directory_capability? :active_directory else :recursive From 724177b86660e3206c9942e62f7d2cba0a39c1fb Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 14:23:09 -0700 Subject: [PATCH 04/15] Update required capability OID to match 2008 R2 v61 --- lib/github/ldap/membership_validators/detect.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index 2ade0e7..6b50dc7 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -6,7 +6,7 @@ module MembershipValidators class Detect < Base # Internal: The capability required to use the ActiveDirectory strategy. # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx. - ACTIVE_DIRECTORY_V51_OID = "1.2.840.113556.1.4.1670".freeze + ACTIVE_DIRECTORY_V61_R2_OID = "1.2.840.113556.1.4.2080".freeze def perform(entry) # short circuit validation if there are no groups to check against @@ -51,7 +51,7 @@ def strategy_config # # Returns true if the host is an ActiveDirectory server, false otherwise. def active_directory_capability? - capabilities[:supportcapabilities].include?(ACTIVE_DIRECTORY_V51_OID) + capabilities[:supportcapabilities].include?(ACTIVE_DIRECTORY_V61_R2_OID) end # Internal: Searches the LDAP host's Root DSE for server capabilities. From fe976b4994615f13bffe11c35df6f94c63253cd1 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 15:56:28 -0700 Subject: [PATCH 05/15] Proactively handle capapbility query errors, instrument --- lib/github/ldap/membership_validators/detect.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index 6b50dc7..b81da9f 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -59,7 +59,16 @@ def active_directory_capability? # Returns the Net::LDAP::Entry object containing the Root DSE # results describing the server capabilities. def capabilities - @capabilities ||= ldap.search_root_dse + @ldap.instrument "capabilities.github_ldap_membership_validator" do |payload| + @capabilities ||= + begin + ldap.search_root_dse + rescue Net::LDAP::LdapError => error + payload[:error] = error + # stubbed result + Net::LDAP::Entry.new + end + end end end end From 941a577c204aded655ed0dc5ded47b8a8b4919a4 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 16:13:04 -0700 Subject: [PATCH 06/15] Fix detect_strategy doco --- lib/github/ldap/membership_validators/detect.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index b81da9f..04eb885 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -26,7 +26,8 @@ def strategy # Internal: Detects LDAP host's capabilities and chooses the best # strategy for the host. # - # If the strategy has been + # If the strategy has been set explicitly, skips detection and uses the + # configured strategy instead. # # Returns the strategy class. def detect_strategy From 60ee236415a6ff2e952b9c24f1a6a58186117419 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 16:13:17 -0700 Subject: [PATCH 07/15] Ensure detect_strategy returns the strategy class --- lib/github/ldap/membership_validators/detect.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index 04eb885..510ecd2 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -35,9 +35,9 @@ def detect_strategy when GitHub::Ldap::MembershipValidators::STRATEGIES.key?(strategy_config) GitHub::Ldap::MembershipValidators::STRATEGIES[strategy_config] when active_directory_capability? - :active_directory + GitHub::Ldap::MembershipValidators::STRATEGIES[:active_directory] else - :recursive + GitHub::Ldap::MembershipValidators::STRATEGIES[:recursive] end end From 18cdf65e8caaf28695c07497335a918fdb16d4ea Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 16:14:49 -0700 Subject: [PATCH 08/15] s/if/whether/ --- lib/github/ldap/membership_validators/detect.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index 510ecd2..3099d86 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -46,7 +46,7 @@ def strategy_config ldap.membership_validator end - # Internal: Detect it the LDAP host is an ActiveDirectory server. + # Internal: Detect whether the LDAP host is an ActiveDirectory server. # # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx. # From 78039780d79a068070bab1fc3836baa73b0026d5 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 16:36:50 -0700 Subject: [PATCH 09/15] Move capabilities lookup to GitHub::Ldap --- lib/github/ldap.rb | 17 +++++++++++++++++ lib/github/ldap/membership_validators/detect.rb | 17 +++-------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/github/ldap.rb b/lib/github/ldap.rb index 19d241a..e556d0b 100644 --- a/lib/github/ldap.rb +++ b/lib/github/ldap.rb @@ -186,6 +186,23 @@ def search(options, &block) end end + # Internal: Searches the host LDAP server's Root DSE for capabilities and + # extensions. + # + # Returns a Net::LDAP::Entry object. + def capabilities + @ldap.instrument "capabilities.github_ldap" do |payload| + @capabilities ||= + begin + @connection.search_root_dse + rescue Net::LDAP::LdapError => error + payload[:error] = error + # stubbed result + Net::LDAP::Entry.new + end + end + end + # Internal - Determine whether to use encryption or not. # # encryption: is the encryption method, either 'ssl', 'tls', 'simple_tls' or 'start_tls'. diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index 3099d86..c48aaeb 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -55,21 +55,10 @@ def active_directory_capability? capabilities[:supportcapabilities].include?(ACTIVE_DIRECTORY_V61_R2_OID) end - # Internal: Searches the LDAP host's Root DSE for server capabilities. - # - # Returns the Net::LDAP::Entry object containing the Root DSE - # results describing the server capabilities. + # Internal: Returns the Net::LDAP::Entry object describing the LDAP + # host's capabilities (via the Root DSE). def capabilities - @ldap.instrument "capabilities.github_ldap_membership_validator" do |payload| - @capabilities ||= - begin - ldap.search_root_dse - rescue Net::LDAP::LdapError => error - payload[:error] = error - # stubbed result - Net::LDAP::Entry.new - end - end + ldap.capabilities end end end From 946267668a44960c21df3e1f33dc0b8e8c0b427c Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 16:46:45 -0700 Subject: [PATCH 10/15] Only instrument when we query for capabilities --- lib/github/ldap.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/github/ldap.rb b/lib/github/ldap.rb index e556d0b..8a952df 100644 --- a/lib/github/ldap.rb +++ b/lib/github/ldap.rb @@ -191,8 +191,8 @@ def search(options, &block) # # Returns a Net::LDAP::Entry object. def capabilities - @ldap.instrument "capabilities.github_ldap" do |payload| - @capabilities ||= + @capabilities ||= + @ldap.instrument "capabilities.github_ldap" do |payload| begin @connection.search_root_dse rescue Net::LDAP::LdapError => error @@ -200,7 +200,7 @@ def capabilities # stubbed result Net::LDAP::Entry.new end - end + end end # Internal - Determine whether to use encryption or not. From 013bb288683feddb67cff3ff27fe4e30e629edbb Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 16:48:49 -0700 Subject: [PATCH 11/15] Server returns supportedCapabilities --- lib/github/ldap/membership_validators/detect.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index c48aaeb..6a3f6dc 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -52,7 +52,7 @@ def strategy_config # # Returns true if the host is an ActiveDirectory server, false otherwise. def active_directory_capability? - capabilities[:supportcapabilities].include?(ACTIVE_DIRECTORY_V61_R2_OID) + capabilities[:supportedcapabilities].include?(ACTIVE_DIRECTORY_V61_R2_OID) end # Internal: Returns the Net::LDAP::Entry object describing the LDAP From 963297ff904f1ad4b999724e75c156899df655c6 Mon Sep 17 00:00:00 2001 From: Matt Todd Date: Fri, 24 Oct 2014 16:49:57 -0700 Subject: [PATCH 12/15] Test Detect meta-strategy --- test/membership_validators/detect_test.rb | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 test/membership_validators/detect_test.rb diff --git a/test/membership_validators/detect_test.rb b/test/membership_validators/detect_test.rb new file mode 100644 index 0000000..8bf522a --- /dev/null +++ b/test/membership_validators/detect_test.rb @@ -0,0 +1,49 @@ +require_relative '../test_helper' + +# NOTE: Since this strategy is targeted at detecting ActiveDirectory +# capabilities, and we don't have AD setup in CI, we stub out actual queries +# and test against what AD *would* respond with. + +class GitHubLdapDetectMembershipValidatorsTest < 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') + @validator = GitHub::Ldap::MembershipValidators::Detect + end + + def make_validator(groups) + groups = @domain.groups(groups) + @validator.new(@ldap, groups) + end + + def test_defers_to_configured_strategy + @ldap.configure_membership_validation_strategy(:classic) + validator = make_validator(%w(group)) + + assert_kind_of GitHub::Ldap::MembershipValidators::Classic, validator.strategy + end + + def test_detects_active_directory + caps = Net::LDAP::Entry.new + caps[:supportedcapabilities] = + [GitHub::Ldap::MembershipValidators::Detect::ACTIVE_DIRECTORY_V61_R2_OID] + + validator = make_validator(%w(group)) + @ldap.stub :capabilities, caps do + assert_kind_of GitHub::Ldap::MembershipValidators::ActiveDirectory, + validator.strategy + end + end + + def test_falls_back_to_recursive + caps = Net::LDAP::Entry.new + caps[:supportedcapabilities] = [] + + validator = make_validator(%w(group)) + @ldap.stub :capabilities, caps do + assert_kind_of GitHub::Ldap::MembershipValidators::Recursive, + validator.strategy + end + end +end From f506dc37456d59efd5e8a8a5410adf91bddfb1ec Mon Sep 17 00:00:00 2001 From: Jerry Cheung Date: Tue, 28 Oct 2014 10:05:53 -0700 Subject: [PATCH 13/15] fix typo --- lib/github/ldap.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/github/ldap.rb b/lib/github/ldap.rb index 8a952df..3258ac4 100644 --- a/lib/github/ldap.rb +++ b/lib/github/ldap.rb @@ -192,7 +192,7 @@ def search(options, &block) # Returns a Net::LDAP::Entry object. def capabilities @capabilities ||= - @ldap.instrument "capabilities.github_ldap" do |payload| + instrument "capabilities.github_ldap" do |payload| begin @connection.search_root_dse rescue Net::LDAP::LdapError => error From ba0792cf1f356090af0445b68364758717e5858f Mon Sep 17 00:00:00 2001 From: Jerry Cheung Date: Tue, 28 Oct 2014 10:07:25 -0700 Subject: [PATCH 14/15] add test for validator detection --- test/ldap_test.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/ldap_test.rb b/test/ldap_test.rb index 40fcb95..250c6bb 100644 --- a/test/ldap_test.rb +++ b/test/ldap_test.rb @@ -72,6 +72,14 @@ def test_instruments_search assert_equal "(uid=user1)", payload[:filter].to_s assert_equal "dc=github,dc=com", payload[:base] end + + def test_membership_validator_default + assert_equal :detect, @ldap.membership_validator + end + + def test_capabilities + assert_kind_of Net::LDAP::Entry, @ldap.capabilities + end end class GitHubLdapTest < GitHub::Ldap::Test From 4a7593252198121fe5fdb5a5794260fcd7f14146 Mon Sep 17 00:00:00 2001 From: Jerry Cheung Date: Tue, 28 Oct 2014 15:30:28 -0700 Subject: [PATCH 15/15] additional docs on AD membership validator --- lib/github/ldap/membership_validators/detect.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/github/ldap/membership_validators/detect.rb b/lib/github/ldap/membership_validators/detect.rb index 6a3f6dc..ba8c4ba 100644 --- a/lib/github/ldap/membership_validators/detect.rb +++ b/lib/github/ldap/membership_validators/detect.rb @@ -2,7 +2,10 @@ module GitHub class Ldap module MembershipValidators # Detects the LDAP host's capabilities and determines the appropriate - # membership validation strategy at runtime. + # membership validation strategy at runtime. Currently detects for + # ActiveDirectory in-chain membership validation. An explicit strategy can + # also be defined via `GitHub::Ldap#membership_validator=`. See also + # `GitHub::Ldap#configure_membership_validation_strategy`. class Detect < Base # Internal: The capability required to use the ActiveDirectory strategy. # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx.