diff --git a/lib/github/ldap.rb b/lib/github/ldap.rb index dc18a44..ebdfddc 100644 --- a/lib/github/ldap.rb +++ b/lib/github/ldap.rb @@ -1,22 +1,26 @@ +require 'net/ldap' +require 'forwardable' + +require 'github/ldap/filter' +require 'github/ldap/domain' +require 'github/ldap/group' +require 'github/ldap/posix_group' +require 'github/ldap/virtual_group' +require 'github/ldap/virtual_attributes' +require 'github/ldap/instrumentation' +require 'github/ldap/member_search' +require 'github/ldap/membership_validators' + module GitHub class Ldap - require 'net/ldap' - require 'forwardable' - require 'github/ldap/filter' - require 'github/ldap/domain' - require 'github/ldap/group' - require 'github/ldap/posix_group' - require 'github/ldap/virtual_group' - require 'github/ldap/virtual_attributes' - require 'github/ldap/instrumentation' - require 'github/ldap/capabilities' - require 'github/ldap/member_search' - require 'github/ldap/membership_validators' - include Instrumentation extend Forwardable + # Internal: The capability required to use ActiveDirectory features. + # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx. + ACTIVE_DIRECTORY_V61_R2_OID = "1.2.840.113556.1.4.2080".freeze + # Utility method to get the last operation result with a human friendly message. # # Returns an OpenStruct with `code` and `message`. @@ -91,11 +95,8 @@ 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]) - - # configure which strategy should be used for member search - configure_member_search_strategy(options[:member_search_strategy]) + # configure both the membership validator and the member search strategies + configure_search_strategy(options[:search_strategy]) # enables instrumenting queries @instrumentation_service = options[:instrumentation_service] @@ -242,42 +243,78 @@ def configure_virtual_attributes(attributes) end end - # Internal: Configure the membership validation strategy. + # Internal: Configure the member search and membership validation strategies. # - # Used by GitHub::Ldap::MembershipValidators::Detect to force a specific - # strategy (instead of detecting host capabilities and deciding at runtime). + # TODO: Inline the logic in these two methods here. + # + # Returns nothing. + def configure_search_strategy(strategy = nil) + # configure which strategy should be used to validate user membership + configure_membership_validation_strategy(strategy) + + # configure which strategy should be used for member search + configure_member_search_strategy(strategy) + end + + # Internal: Configure the membership validation strategy. # - # If `strategy` is not provided, or doesn't match a known strategy, - # defaults to `:detect`. Otherwise the configured strategy is selected. + # If no known strategy is provided, detects ActiveDirectory capabilities or + # falls back to the Recursive strategy by default. # - # Returns the selected membership validator strategy Symbol. + # Returns the membership validator strategy Class. def configure_membership_validation_strategy(strategy = nil) @membership_validator = case strategy.to_s - when "classic", "recursive", "active_directory" - strategy.to_sym + when "classic" + GitHub::Ldap::MembershipValidators::Classic + when "recursive" + GitHub::Ldap::MembershipValidators::Recursive + when "active_directory" + GitHub::Ldap::MembershipValidators::ActiveDirectory else - :detect + # fallback to detection, defaulting to recursive strategy + if active_directory_capability? + GitHub::Ldap::MembershipValidators::ActiveDirectory + else + GitHub::Ldap::MembershipValidators::Recursive + end end end # Internal: Configure the member search strategy. # - # Used by GitHub::Ldap::MemberSearch::Detect to force a specific strategy - # (instead of detecting the 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. + # If no known strategy is provided, detects ActiveDirectory capabilities or + # falls back to the Recursive strategy by default. # - # Returns the selected strategy Symbol. + # Returns the selected strategy Class. def configure_member_search_strategy(strategy = nil) @member_search_strategy = - case strategy.to_s - when "classic", "recursive" - strategy.to_sym - else - :detect - end + case strategy.to_s + when "classic" + GitHub::Ldap::MemberSearch::Classic + when "recursive" + GitHub::Ldap::MemberSearch::Recursive + when "active_directory" + GitHub::Ldap::MemberSearch::ActiveDirectory + else + # fallback to detection, defaulting to recursive strategy + if active_directory_capability? + GitHub::Ldap::MemberSearch::ActiveDirectory + else + GitHub::Ldap::MemberSearch::Recursive + end + end + end + + # Internal: Detect whether 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[:supportedcapabilities].include?(ACTIVE_DIRECTORY_V61_R2_OID) end + private :active_directory_capability? end end diff --git a/lib/github/ldap/capabilities.rb b/lib/github/ldap/capabilities.rb deleted file mode 100644 index 370be4b..0000000 --- a/lib/github/ldap/capabilities.rb +++ /dev/null @@ -1,24 +0,0 @@ -module GitHub - class Ldap - module Capabilities - # Internal: The capability required to use the ActiveDirectory strategy. - # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx. - ACTIVE_DIRECTORY_V61_R2_OID = "1.2.840.113556.1.4.2080".freeze - - # Internal: Detect whether 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[:supportedcapabilities].include?(ACTIVE_DIRECTORY_V61_R2_OID) - end - - # Internal: Returns the Net::LDAP::Entry object describing the LDAP - # host's capabilities (via the Root DSE). - def capabilities - ldap.capabilities - end - end - end -end diff --git a/lib/github/ldap/member_search.rb b/lib/github/ldap/member_search.rb index 6269638..d051268 100644 --- a/lib/github/ldap/member_search.rb +++ b/lib/github/ldap/member_search.rb @@ -1,26 +1,4 @@ require 'github/ldap/member_search/base' -require 'github/ldap/member_search/detect' require 'github/ldap/member_search/classic' require 'github/ldap/member_search/recursive' require 'github/ldap/member_search/active_directory' - -module GitHub - class Ldap - # Provides various strategies for member lookup. - # - # For example: - # - # group = domain.groups(%w(Engineering)).first - # strategy = GitHub::Ldap::MemberSearch::Recursive.new(ldap) - # strategy.perform(group) #=> [#] - # - module MemberSearch - # Internal: Mapping of strategy name to class. - STRATEGIES = { - :classic => GitHub::Ldap::MemberSearch::Classic, - :recursive => GitHub::Ldap::MemberSearch::Recursive, - :active_directory => GitHub::Ldap::MemberSearch::ActiveDirectory - } - end - end -end diff --git a/lib/github/ldap/member_search/detect.rb b/lib/github/ldap/member_search/detect.rb deleted file mode 100644 index 06cdb32..0000000 --- a/lib/github/ldap/member_search/detect.rb +++ /dev/null @@ -1,71 +0,0 @@ -module GitHub - class Ldap - module MemberSearch - # Detects the LDAP host's capabilities and determines the appropriate - # member search strategy at runtime. - # - # Currently detects for ActiveDirectory in-chain membership validation. - # - # An explicit strategy can also be defined via - # `GitHub::Ldap#member_search_strategy=`. - # - # See also `GitHub::Ldap#configure_member_search_strategy`. - class Detect - # Defines `active_directory_capability?` and necessary helpers. - include GitHub::Ldap::Capabilities - - # Internal: The GitHub::Ldap object to search domains with. - attr_reader :ldap - - # Internal: The Hash of options to pass through to the strategy. - attr_reader :options - - # Public: Instantiate a meta strategy to detect the right strategy - # to use for the search, and call that strategy, at runtime. - # - # - ldap: GitHub::Ldap object - # - options: Hash of options (passed through) - def initialize(ldap, options = {}) - @ldap = ldap - @options = options - end - - # Public: Performs search for group members via the appropriate search - # strategy detected/configured. - # - # Returns Array of Net::LDAP::Entry objects. - def perform(entry) - strategy.perform(entry) - end - - # Internal: Returns the member search strategy object. - def strategy - @strategy ||= begin - strategy = detect_strategy - strategy.new(ldap, options) - end - end - - # Internal: Find the most appropriate search strategy, either by - # configuration or by detecting the host's capabilities. - # - # Returns the strategy class. - def detect_strategy - case - when GitHub::Ldap::MemberSearch::STRATEGIES.key?(strategy_config) - GitHub::Ldap::MemberSearch::STRATEGIES[strategy_config] - when active_directory_capability? - GitHub::Ldap::MemberSearch::STRATEGIES[:active_directory] - else - GitHub::Ldap::MemberSearch::STRATEGIES[:recursive] - end - end - - # Internal: Returns the configured member search strategy Symbol. - def strategy_config - ldap.member_search_strategy - end - end - end - end -end diff --git a/lib/github/ldap/membership_validators.rb b/lib/github/ldap/membership_validators.rb index 1135a2d..c629a37 100644 --- a/lib/github/ldap/membership_validators.rb +++ b/lib/github/ldap/membership_validators.rb @@ -1,26 +1,4 @@ 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' - -module GitHub - class Ldap - # Provides various strategies for validating membership. - # - # For example: - # - # groups = domain.groups(%w(Engineering)) - # validator = GitHub::Ldap::MembershipValidators::Classic.new(ldap, groups) - # validator.perform(entry) #=> true - # - 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 deleted file mode 100644 index 74f7f0d..0000000 --- a/lib/github/ldap/membership_validators/detect.rb +++ /dev/null @@ -1,53 +0,0 @@ -module GitHub - class Ldap - module MembershipValidators - # Detects the LDAP host's capabilities and determines the appropriate - # 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 - # Defines `active_directory_capability?` and necessary helpers. - include GitHub::Ldap::Capabilities - - 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 set explicitly, skips detection and uses the - # configured strategy instead. - # - # Returns the strategy class. - def detect_strategy - case - when GitHub::Ldap::MembershipValidators::STRATEGIES.key?(strategy_config) - GitHub::Ldap::MembershipValidators::STRATEGIES[strategy_config] - when active_directory_capability? - GitHub::Ldap::MembershipValidators::STRATEGIES[:active_directory] - else - GitHub::Ldap::MembershipValidators::STRATEGIES[:recursive] - end - end - - # Internal: Returns the configured membership validator strategy Symbol. - def strategy_config - ldap.membership_validator - end - end - end - end -end diff --git a/test/ldap_test.rb b/test/ldap_test.rb index 6d4d5fb..48511d8 100644 --- a/test/ldap_test.rb +++ b/test/ldap_test.rb @@ -73,28 +73,45 @@ def test_instruments_search assert_equal "dc=github,dc=com", payload[:base] end - def test_membership_validator_default - assert_equal :detect, @ldap.membership_validator + def test_search_strategy_defaults + assert_equal GitHub::Ldap::MembershipValidators::Recursive, @ldap.membership_validator + assert_equal GitHub::Ldap::MemberSearch::Recursive, @ldap.member_search_strategy end - def test_membership_validator_configured_to_classic_strategy - @ldap.configure_membership_validation_strategy :classic - assert_equal :classic, @ldap.membership_validator + def test_search_strategy_detects_active_directory + caps = Net::LDAP::Entry.new + caps[:supportedcapabilities] = [GitHub::Ldap::ACTIVE_DIRECTORY_V61_R2_OID] + + @ldap.stub :capabilities, caps do + @ldap.configure_search_strategy :detect + + assert_equal GitHub::Ldap::MembershipValidators::ActiveDirectory, @ldap.membership_validator + assert_equal GitHub::Ldap::MemberSearch::ActiveDirectory, @ldap.member_search_strategy + end + end + + def test_search_strategy_configured_to_classic + @ldap.configure_search_strategy :classic + assert_equal GitHub::Ldap::MembershipValidators::Classic, @ldap.membership_validator + assert_equal GitHub::Ldap::MemberSearch::Classic, @ldap.member_search_strategy end - def test_membership_validator_configured_to_recursive_strategy - @ldap.configure_membership_validation_strategy :recursive - assert_equal :recursive, @ldap.membership_validator + def test_search_strategy_configured_to_recursive + @ldap.configure_search_strategy :recursive + assert_equal GitHub::Ldap::MembershipValidators::Recursive, @ldap.membership_validator + assert_equal GitHub::Ldap::MemberSearch::Recursive, @ldap.member_search_strategy end - def test_membership_validator_configured_to_active_directory_strategy - @ldap.configure_membership_validation_strategy :active_directory - assert_equal :active_directory, @ldap.membership_validator + def test_search_strategy_configured_to_active_directory + @ldap.configure_search_strategy :active_directory + assert_equal GitHub::Ldap::MembershipValidators::ActiveDirectory, @ldap.membership_validator + assert_equal GitHub::Ldap::MemberSearch::ActiveDirectory, @ldap.member_search_strategy end - def test_membership_validator_misconfigured_to_unrecognized_strategy_falls_back_to_default - @ldap.configure_membership_validation_strategy :unknown - assert_equal :detect, @ldap.membership_validator + def test_search_strategy_misconfigured_to_unrecognized_strategy_falls_back_to_default + @ldap.configure_search_strategy :unknown + assert_equal GitHub::Ldap::MembershipValidators::Recursive, @ldap.membership_validator + assert_equal GitHub::Ldap::MemberSearch::Recursive, @ldap.member_search_strategy end def test_capabilities diff --git a/test/member_search/detect_test.rb b/test/member_search/detect_test.rb deleted file mode 100644 index 6092cdf..0000000 --- a/test/member_search/detect_test.rb +++ /dev/null @@ -1,42 +0,0 @@ -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 GitHubLdapDetectMemberSearchTest < GitHub::Ldap::Test - include GitHub::Ldap::Capabilities - - 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::MemberSearch::Detect.new(@ldap) - end - - def test_defers_to_configured_strategy - @ldap.configure_member_search_strategy(:classic) - - assert_kind_of GitHub::Ldap::MemberSearch::Classic, @strategy.strategy - end - - def test_detects_active_directory - caps = Net::LDAP::Entry.new - caps[:supportedcapabilities] = [ACTIVE_DIRECTORY_V61_R2_OID] - - @ldap.stub :capabilities, caps do - assert_kind_of GitHub::Ldap::MemberSearch::ActiveDirectory, - @strategy.strategy - end - end - - def test_falls_back_to_recursive - caps = Net::LDAP::Entry.new - caps[:supportedcapabilities] = [] - - @ldap.stub :capabilities, caps do - assert_kind_of GitHub::Ldap::MemberSearch::Recursive, - @strategy.strategy - end - end -end diff --git a/test/membership_validators/detect_test.rb b/test/membership_validators/detect_test.rb deleted file mode 100644 index 34d476a..0000000 --- a/test/membership_validators/detect_test.rb +++ /dev/null @@ -1,50 +0,0 @@ -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 - include GitHub::Ldap::Capabilities - - 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] = [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