Skip to content

Commit 85781c4

Browse files
committed
Merge pull request #58 from github/default-membership-validator
Detect appropriate membership validator strategy by default
2 parents 6a37d68 + 56e1fd5 commit 85781c4

File tree

5 files changed

+175
-1
lines changed

5 files changed

+175
-1
lines changed

lib/github/ldap.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class Ldap
3434
def_delegator :@connection, :open
3535

3636
attr_reader :uid, :search_domains, :virtual_attributes,
37+
:membership_validator,
3738
:instrumentation_service
3839

3940
# Build a new GitHub::Ldap instance
@@ -87,6 +88,9 @@ def initialize(options = {})
8788
# when a base is not explicitly provided.
8889
@search_domains = Array(options[:search_domains])
8990

91+
# configure which strategy should be used to validate user membership
92+
configure_membership_validation_strategy(options[:membership_validator])
93+
9094
# enables instrumenting queries
9195
@instrumentation_service = options[:instrumentation_service]
9296
end
@@ -182,6 +186,23 @@ def search(options, &block)
182186
end
183187
end
184188

189+
# Internal: Searches the host LDAP server's Root DSE for capabilities and
190+
# extensions.
191+
#
192+
# Returns a Net::LDAP::Entry object.
193+
def capabilities
194+
@capabilities ||=
195+
instrument "capabilities.github_ldap" do |payload|
196+
begin
197+
@connection.search_root_dse
198+
rescue Net::LDAP::LdapError => error
199+
payload[:error] = error
200+
# stubbed result
201+
Net::LDAP::Entry.new
202+
end
203+
end
204+
end
205+
185206
# Internal - Determine whether to use encryption or not.
186207
#
187208
# encryption: is the encryption method, either 'ssl', 'tls', 'simple_tls' or 'start_tls'.
@@ -214,5 +235,24 @@ def configure_virtual_attributes(attributes)
214235
VirtualAttributes.new(false)
215236
end
216237
end
238+
239+
# Internal: Configure the membership validation strategy.
240+
#
241+
# Used by GitHub::Ldap::MembershipValidators::Detect to force a specific
242+
# strategy (instead of detecting host capabilities and deciding at runtime).
243+
#
244+
# If `strategy` is not provided, or doesn't match a known strategy,
245+
# defaults to `:detect`. Otherwise the configured strategy is selected.
246+
#
247+
# Returns the selected membership validator strategy Symbol.
248+
def configure_membership_validation_strategy(strategy = nil)
249+
@membership_validator =
250+
case strategy.to_s
251+
when "classic", "recursive", "active_directory"
252+
strategy.to_sym
253+
else
254+
:detect
255+
end
256+
end
217257
end
218258
end

lib/github/ldap/membership_validators.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'github/ldap/membership_validators/base'
2+
require 'github/ldap/membership_validators/detect'
23
require 'github/ldap/membership_validators/classic'
34
require 'github/ldap/membership_validators/recursive'
45
require 'github/ldap/membership_validators/active_directory'
@@ -13,6 +14,13 @@ class Ldap
1314
# validator = GitHub::Ldap::MembershipValidators::Classic.new(ldap, groups)
1415
# validator.perform(entry) #=> true
1516
#
16-
module MembershipValidators; end
17+
module MembershipValidators
18+
# Internal: Mapping of strategy name to class.
19+
STRATEGIES = {
20+
:classic => GitHub::Ldap::MembershipValidators::Classic,
21+
:recursive => GitHub::Ldap::MembershipValidators::Recursive,
22+
:active_directory => GitHub::Ldap::MembershipValidators::ActiveDirectory
23+
}
24+
end
1725
end
1826
end
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module GitHub
2+
class Ldap
3+
module MembershipValidators
4+
# Detects the LDAP host's capabilities and determines the appropriate
5+
# membership validation strategy at runtime. Currently detects for
6+
# ActiveDirectory in-chain membership validation. An explicit strategy can
7+
# also be defined via `GitHub::Ldap#membership_validator=`. See also
8+
# `GitHub::Ldap#configure_membership_validation_strategy`.
9+
class Detect < Base
10+
# Internal: The capability required to use the ActiveDirectory strategy.
11+
# See: http://msdn.microsoft.com/en-us/library/cc223359.aspx.
12+
ACTIVE_DIRECTORY_V61_R2_OID = "1.2.840.113556.1.4.2080".freeze
13+
14+
def perform(entry)
15+
# short circuit validation if there are no groups to check against
16+
return true if groups.empty?
17+
18+
strategy.perform(entry)
19+
end
20+
21+
# Internal: Returns the membership validation strategy object.
22+
def strategy
23+
@strategy ||= begin
24+
strategy = detect_strategy
25+
strategy.new(ldap, groups)
26+
end
27+
end
28+
29+
# Internal: Detects LDAP host's capabilities and chooses the best
30+
# strategy for the host.
31+
#
32+
# If the strategy has been set explicitly, skips detection and uses the
33+
# configured strategy instead.
34+
#
35+
# Returns the strategy class.
36+
def detect_strategy
37+
case
38+
when GitHub::Ldap::MembershipValidators::STRATEGIES.key?(strategy_config)
39+
GitHub::Ldap::MembershipValidators::STRATEGIES[strategy_config]
40+
when active_directory_capability?
41+
GitHub::Ldap::MembershipValidators::STRATEGIES[:active_directory]
42+
else
43+
GitHub::Ldap::MembershipValidators::STRATEGIES[:recursive]
44+
end
45+
end
46+
47+
# Internal: Returns the configured membership validator strategy Symbol.
48+
def strategy_config
49+
ldap.membership_validator
50+
end
51+
52+
# Internal: Detect whether the LDAP host is an ActiveDirectory server.
53+
#
54+
# See: http://msdn.microsoft.com/en-us/library/cc223359.aspx.
55+
#
56+
# Returns true if the host is an ActiveDirectory server, false otherwise.
57+
def active_directory_capability?
58+
capabilities[:supportedcapabilities].include?(ACTIVE_DIRECTORY_V61_R2_OID)
59+
end
60+
61+
# Internal: Returns the Net::LDAP::Entry object describing the LDAP
62+
# host's capabilities (via the Root DSE).
63+
def capabilities
64+
ldap.capabilities
65+
end
66+
end
67+
end
68+
end
69+
end

test/ldap_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ def test_instruments_search
7272
assert_equal "(uid=user1)", payload[:filter].to_s
7373
assert_equal "dc=github,dc=com", payload[:base]
7474
end
75+
76+
def test_membership_validator_default
77+
assert_equal :detect, @ldap.membership_validator
78+
end
79+
80+
def test_capabilities
81+
assert_kind_of Net::LDAP::Entry, @ldap.capabilities
82+
end
7583
end
7684

7785
class GitHubLdapTest < GitHub::Ldap::Test
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
require_relative '../test_helper'
2+
3+
# NOTE: Since this strategy is targeted at detecting ActiveDirectory
4+
# capabilities, and we don't have AD setup in CI, we stub out actual queries
5+
# and test against what AD *would* respond with.
6+
7+
class GitHubLdapDetectMembershipValidatorsTest < GitHub::Ldap::Test
8+
def setup
9+
@ldap = GitHub::Ldap.new(options.merge(search_domains: %w(dc=github,dc=com)))
10+
@domain = @ldap.domain("dc=github,dc=com")
11+
@entry = @domain.user?('user1')
12+
@validator = GitHub::Ldap::MembershipValidators::Detect
13+
end
14+
15+
def make_validator(groups)
16+
groups = @domain.groups(groups)
17+
@validator.new(@ldap, groups)
18+
end
19+
20+
def test_defers_to_configured_strategy
21+
@ldap.configure_membership_validation_strategy(:classic)
22+
validator = make_validator(%w(group))
23+
24+
assert_kind_of GitHub::Ldap::MembershipValidators::Classic, validator.strategy
25+
end
26+
27+
def test_detects_active_directory
28+
caps = Net::LDAP::Entry.new
29+
caps[:supportedcapabilities] =
30+
[GitHub::Ldap::MembershipValidators::Detect::ACTIVE_DIRECTORY_V61_R2_OID]
31+
32+
validator = make_validator(%w(group))
33+
@ldap.stub :capabilities, caps do
34+
assert_kind_of GitHub::Ldap::MembershipValidators::ActiveDirectory,
35+
validator.strategy
36+
end
37+
end
38+
39+
def test_falls_back_to_recursive
40+
caps = Net::LDAP::Entry.new
41+
caps[:supportedcapabilities] = []
42+
43+
validator = make_validator(%w(group))
44+
@ldap.stub :capabilities, caps do
45+
assert_kind_of GitHub::Ldap::MembershipValidators::Recursive,
46+
validator.strategy
47+
end
48+
end
49+
end

0 commit comments

Comments
 (0)