github-ldap 1.5.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f94f5af944845beec077b7ae4f67f73bcdcd3324
4
- data.tar.gz: d0cbf4bb74c1fedfce35a886967b084061c95cfb
3
+ metadata.gz: 13b19164520b8ed69dc3defdfa76b747767d9006
4
+ data.tar.gz: f34f58a9d8d91a89b5f9873b7a17daf251b9ac3d
5
5
  SHA512:
6
- metadata.gz: cd8ca33063aff485c5e0d4df63ca8e5d6c83641d202d3dddc02ace9d373710e511f152197a2d9c36e71e9dfe67ce56692859183a7ced2fe7fb847caeb2fcc1bb
7
- data.tar.gz: 75a413369b9d935499bae900382569314b90f71259ac5b813cd059328ca92f6533acdb106947c7d9139228f4f3597599d405a4be5bada4609d1aaef7e43c521d
6
+ metadata.gz: 7c492ec6ceba65de3683871843f7c1ae878e08dea0be24cbe1096723858d7b6b68bb37e7ab1a27c60453e65d4cac26b1bd84f3c79206cbcb5c289f03cf945a67
7
+ data.tar.gz: 4b8bc4c463f7b29b12983e245615f99f6908be16d6bc491b06a7433d86ef2d482bf0f8da647834e176ed1d4128afc6e5cc521811c7a17ce9c028405923c924b6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v1.6.0
4
+
5
+ * Expose `GitHub::Ldap::Group.group?` for testing if entry is a group [#67](https://github.com/github/github-ldap/pull/67)
6
+ * Add *Member Search* strategies [#64](https://github.com/github/github-ldap/pull/64) [#68](https://github.com/github/github-ldap/pull/68) [#69](https://github.com/github/github-ldap/pull/69)
7
+ * Simplify *Member Search* and *Membership Validation* search strategy configuration, detection, and default behavior [#70](https://github.com/github/github-ldap/pull/70)
8
+
3
9
  ## v1.5.0
4
10
 
5
11
  * Automatically detect membership validator strategy by default [#58](https://github.com/github/github-ldap/pull/58) [#62](https://github.com/github/github-ldap/pull/62)
data/github-ldap.gemspec CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "github-ldap"
5
- spec.version = "1.5.0"
6
- spec.authors = ["David Calavera"]
7
- spec.email = ["david.calavera@gmail.com"]
8
- spec.description = %q{Ldap authentication for humans}
9
- spec.summary = %q{Ldap client authentication wrapper without all the boilerplate}
5
+ spec.version = "1.6.0"
6
+ spec.authors = ["David Calavera", "Matt Todd"]
7
+ spec.email = ["david.calavera@gmail.com", "chiology@gmail.com"]
8
+ spec.description = %q{LDAP authentication for humans}
9
+ spec.summary = %q{LDAP client authentication wrapper without all the boilerplate}
10
10
  spec.homepage = "https://github.com/github/github-ldap"
11
11
  spec.license = "MIT"
12
12
 
data/lib/github/ldap.rb CHANGED
@@ -1,20 +1,26 @@
1
+ require 'net/ldap'
2
+ require 'forwardable'
3
+
4
+ require 'github/ldap/filter'
5
+ require 'github/ldap/domain'
6
+ require 'github/ldap/group'
7
+ require 'github/ldap/posix_group'
8
+ require 'github/ldap/virtual_group'
9
+ require 'github/ldap/virtual_attributes'
10
+ require 'github/ldap/instrumentation'
11
+ require 'github/ldap/member_search'
12
+ require 'github/ldap/membership_validators'
13
+
1
14
  module GitHub
2
15
  class Ldap
3
- require 'net/ldap'
4
- require 'forwardable'
5
- require 'github/ldap/filter'
6
- require 'github/ldap/domain'
7
- require 'github/ldap/group'
8
- require 'github/ldap/posix_group'
9
- require 'github/ldap/virtual_group'
10
- require 'github/ldap/virtual_attributes'
11
- require 'github/ldap/instrumentation'
12
- require 'github/ldap/membership_validators'
13
-
14
16
  include Instrumentation
15
17
 
16
18
  extend Forwardable
17
19
 
20
+ # Internal: The capability required to use ActiveDirectory features.
21
+ # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx.
22
+ ACTIVE_DIRECTORY_V61_R2_OID = "1.2.840.113556.1.4.2080".freeze
23
+
18
24
  # Utility method to get the last operation result with a human friendly message.
19
25
  #
20
26
  # Returns an OpenStruct with `code` and `message`.
@@ -35,6 +41,7 @@ module GitHub
35
41
 
36
42
  attr_reader :uid, :search_domains, :virtual_attributes,
37
43
  :membership_validator,
44
+ :member_search_strategy,
38
45
  :instrumentation_service
39
46
 
40
47
  # Build a new GitHub::Ldap instance
@@ -88,8 +95,8 @@ module GitHub
88
95
  # when a base is not explicitly provided.
89
96
  @search_domains = Array(options[:search_domains])
90
97
 
91
- # configure which strategy should be used to validate user membership
92
- configure_membership_validation_strategy(options[:membership_validator])
98
+ # configure both the membership validator and the member search strategies
99
+ configure_search_strategy(options[:search_strategy])
93
100
 
94
101
  # enables instrumenting queries
95
102
  @instrumentation_service = options[:instrumentation_service]
@@ -236,23 +243,78 @@ module GitHub
236
243
  end
237
244
  end
238
245
 
239
- # Internal: Configure the membership validation strategy.
246
+ # Internal: Configure the member search and membership validation strategies.
247
+ #
248
+ # TODO: Inline the logic in these two methods here.
240
249
  #
241
- # Used by GitHub::Ldap::MembershipValidators::Detect to force a specific
242
- # strategy (instead of detecting host capabilities and deciding at runtime).
250
+ # Returns nothing.
251
+ def configure_search_strategy(strategy = nil)
252
+ # configure which strategy should be used to validate user membership
253
+ configure_membership_validation_strategy(strategy)
254
+
255
+ # configure which strategy should be used for member search
256
+ configure_member_search_strategy(strategy)
257
+ end
258
+
259
+ # Internal: Configure the membership validation strategy.
243
260
  #
244
- # If `strategy` is not provided, or doesn't match a known strategy,
245
- # defaults to `:detect`. Otherwise the configured strategy is selected.
261
+ # If no known strategy is provided, detects ActiveDirectory capabilities or
262
+ # falls back to the Recursive strategy by default.
246
263
  #
247
- # Returns the selected membership validator strategy Symbol.
264
+ # Returns the membership validator strategy Class.
248
265
  def configure_membership_validation_strategy(strategy = nil)
249
266
  @membership_validator =
250
267
  case strategy.to_s
251
- when "classic", "recursive", "active_directory"
252
- strategy.to_sym
268
+ when "classic"
269
+ GitHub::Ldap::MembershipValidators::Classic
270
+ when "recursive"
271
+ GitHub::Ldap::MembershipValidators::Recursive
272
+ when "active_directory"
273
+ GitHub::Ldap::MembershipValidators::ActiveDirectory
274
+ else
275
+ # fallback to detection, defaulting to recursive strategy
276
+ if active_directory_capability?
277
+ GitHub::Ldap::MembershipValidators::ActiveDirectory
278
+ else
279
+ GitHub::Ldap::MembershipValidators::Recursive
280
+ end
281
+ end
282
+ end
283
+
284
+ # Internal: Configure the member search strategy.
285
+ #
286
+ #
287
+ # If no known strategy is provided, detects ActiveDirectory capabilities or
288
+ # falls back to the Recursive strategy by default.
289
+ #
290
+ # Returns the selected strategy Class.
291
+ def configure_member_search_strategy(strategy = nil)
292
+ @member_search_strategy =
293
+ case strategy.to_s
294
+ when "classic"
295
+ GitHub::Ldap::MemberSearch::Classic
296
+ when "recursive"
297
+ GitHub::Ldap::MemberSearch::Recursive
298
+ when "active_directory"
299
+ GitHub::Ldap::MemberSearch::ActiveDirectory
253
300
  else
254
- :detect
301
+ # fallback to detection, defaulting to recursive strategy
302
+ if active_directory_capability?
303
+ GitHub::Ldap::MemberSearch::ActiveDirectory
304
+ else
305
+ GitHub::Ldap::MemberSearch::Recursive
306
+ end
255
307
  end
256
308
  end
309
+
310
+ # Internal: Detect whether the LDAP host is an ActiveDirectory server.
311
+ #
312
+ # See: http://msdn.microsoft.com/en-us/library/cc223359.aspx.
313
+ #
314
+ # Returns true if the host is an ActiveDirectory server, false otherwise.
315
+ def active_directory_capability?
316
+ capabilities[:supportedcapabilities].include?(ACTIVE_DIRECTORY_V61_R2_OID)
317
+ end
318
+ private :active_directory_capability?
257
319
  end
258
320
  end
@@ -163,8 +163,11 @@ module GitHub
163
163
  # Get the entry for this domain.
164
164
  #
165
165
  # Returns a Net::LDAP::Entry
166
- def bind
167
- search(size: 1, scope: Net::LDAP::SearchScope_BaseObject).first
166
+ def bind(options = {})
167
+ options[:size] = 1
168
+ options[:scope] = Net::LDAP::SearchScope_BaseObject
169
+ options[:attributes] ||= []
170
+ search(options).first
168
171
  end
169
172
  end
170
173
  end
@@ -69,6 +69,11 @@ module GitHub
69
69
  end
70
70
  end
71
71
 
72
+ # Internal: Returns true if the object class(es) provided match a group's.
73
+ def group?(object_class)
74
+ self.class.group?(object_class)
75
+ end
76
+
72
77
  # Internal - Check if an object class includes the member names
73
78
  # Use `&` rathen than `include?` because both are arrays.
74
79
  #
@@ -76,7 +81,7 @@ module GitHub
76
81
  # will fail to match correctly unless we also downcase our group classes.
77
82
  #
78
83
  # Returns true if the object class includes one of the group class names.
79
- def group?(object_class)
84
+ def self.group?(object_class)
80
85
  !(GROUP_CLASS_NAMES.map(&:downcase) & object_class.map(&:downcase)).empty?
81
86
  end
82
87
 
@@ -0,0 +1,4 @@
1
+ require 'github/ldap/member_search/base'
2
+ require 'github/ldap/member_search/classic'
3
+ require 'github/ldap/member_search/recursive'
4
+ require 'github/ldap/member_search/active_directory'
@@ -0,0 +1,60 @@
1
+ module GitHub
2
+ class Ldap
3
+ module MemberSearch
4
+ # Look up group members using the ActiveDirectory "in chain" matching rule.
5
+ #
6
+ # The 1.2.840.113556.1.4.1941 matching rule (LDAP_MATCHING_RULE_IN_CHAIN)
7
+ # "walks the chain of ancestry in objects all the way to the root until
8
+ # it finds a match".
9
+ # Source: http://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx
10
+ #
11
+ # This means we have an efficient method of searching for group members,
12
+ # even in nested groups, performed on the server side.
13
+ class ActiveDirectory < Base
14
+ OID = "1.2.840.113556.1.4.1941"
15
+
16
+ # Internal: The default attributes to query for.
17
+ # NOTE: We technically don't need any by default, but if we left this
18
+ # empty, we'd be querying for *all* attributes which is less ideal.
19
+ DEFAULT_ATTRS = %w(objectClass)
20
+
21
+ # Internal: The attributes to search for.
22
+ attr_reader :attrs
23
+
24
+ # Public: Instantiate new search strategy.
25
+ #
26
+ # - ldap: GitHub::Ldap object
27
+ # - options: Hash of options
28
+ #
29
+ # NOTE: This overrides default behavior to configure attrs`.
30
+ def initialize(ldap, options = {})
31
+ super
32
+ @attrs = Array(options[:attrs]).concat DEFAULT_ATTRS
33
+ end
34
+
35
+ # Public: Performs search for group members, including groups and
36
+ # members of subgroups, using ActiveDirectory's "in chain" matching
37
+ # rule.
38
+ #
39
+ # Returns Array of Net::LDAP::Entry objects.
40
+ def perform(group)
41
+ filter = member_of_in_chain_filter(group)
42
+
43
+ # search for all members of the group, including subgroups, by
44
+ # searching "in chain".
45
+ domains.each_with_object([]) do |domain, members|
46
+ members.concat domain.search(filter: filter, attributes: attrs)
47
+ end
48
+ end
49
+
50
+ # Internal: Constructs a member filter using the "in chain"
51
+ # extended matching rule afforded by ActiveDirectory.
52
+ #
53
+ # Returns a Net::LDAP::Filter object.
54
+ def member_of_in_chain_filter(entry)
55
+ Net::LDAP::Filter.ex("memberOf:#{OID}", entry.dn)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ module GitHub
2
+ class Ldap
3
+ module MemberSearch
4
+ class Base
5
+
6
+ # Internal: The GitHub::Ldap object to search domains with.
7
+ attr_reader :ldap
8
+
9
+ # Public: Instantiate new search strategy.
10
+ #
11
+ # - ldap: GitHub::Ldap object
12
+ # - options: Hash of options
13
+ def initialize(ldap, options = {})
14
+ @ldap = ldap
15
+ @options = options
16
+ end
17
+
18
+ # Public: Abstract: Performs search for group members.
19
+ #
20
+ # Returns Array of Net::LDAP::Entry objects.
21
+ # def perform(entry)
22
+ # end
23
+
24
+ # Internal: Domains to search through.
25
+ #
26
+ # Returns an Array of GitHub::Ldap::Domain objects.
27
+ def domains
28
+ @domains ||= ldap.search_domains.map { |base| ldap.domain(base) }
29
+ end
30
+ private :domains
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ module GitHub
2
+ class Ldap
3
+ module MemberSearch
4
+ # Look up group members using the existing `Group#members` and
5
+ # `Group#subgroups` API.
6
+ class Classic < Base
7
+ # Public: Performs search for group members, including groups and
8
+ # members of subgroups recursively.
9
+ #
10
+ # Returns Array of Net::LDAP::Entry objects.
11
+ def perform(group_entry)
12
+ group = ldap.load_group(group_entry)
13
+ group.members + group.subgroups
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,133 @@
1
+ module GitHub
2
+ class Ldap
3
+ module MemberSearch
4
+ # Look up group members recursively.
5
+ #
6
+ # This results in a maximum of `depth` iterations/recursions to look up
7
+ # members of a group and its subgroups.
8
+ class Recursive < Base
9
+ include Filter
10
+
11
+ DEFAULT_MAX_DEPTH = 9
12
+ DEFAULT_ATTRS = %w(member uniqueMember memberUid)
13
+
14
+ # Internal: The maximum depth to search for members.
15
+ attr_reader :depth
16
+
17
+ # Internal: The attributes to search for.
18
+ attr_reader :attrs
19
+
20
+ # Public: Instantiate new search strategy.
21
+ #
22
+ # - ldap: GitHub::Ldap object
23
+ # - options: Hash of options
24
+ #
25
+ # NOTE: This overrides default behavior to configure `depth` and `attrs`.
26
+ def initialize(ldap, options = {})
27
+ super
28
+ @depth = options[:depth] || DEFAULT_MAX_DEPTH
29
+ @attrs = Array(options[:attrs]).concat DEFAULT_ATTRS
30
+ end
31
+
32
+ # Public: Performs search for group members, including groups and
33
+ # members of subgroups recursively.
34
+ #
35
+ # Returns Array of Net::LDAP::Entry objects.
36
+ def perform(group)
37
+ found = Hash.new
38
+
39
+ # find members (N queries)
40
+ entries = member_entries(group)
41
+ return [] if entries.empty?
42
+
43
+ # track found entries
44
+ entries.each do |entry|
45
+ found[entry.dn] = entry
46
+ end
47
+
48
+ # descend to `depth` levels, at most
49
+ depth.times do |n|
50
+ # find every (new, unique) member entry
51
+ depth_subentries = entries.each_with_object([]) do |entry, depth_entries|
52
+ submembers = entry["member"]
53
+
54
+ # skip any members we've already found
55
+ submembers.reject! { |dn| found.key?(dn) }
56
+
57
+ # find members of subgroup, including subgroups (N queries)
58
+ subentries = member_entries(entry)
59
+ next if subentries.empty?
60
+
61
+ # track found subentries
62
+ subentries.each { |entry| found[entry.dn] = entry }
63
+
64
+ # collect all entries for this depth
65
+ depth_entries.concat subentries
66
+ end
67
+
68
+ # stop if there are no more subgroups to search
69
+ break if depth_subentries.empty?
70
+
71
+ # go one level deeper
72
+ entries = depth_subentries
73
+ end
74
+
75
+ # return all found entries
76
+ found.values
77
+ end
78
+
79
+ # Internal: Fetch member entries, including subgroups, for the given
80
+ # entry.
81
+ #
82
+ # Returns an Array of Net::LDAP::Entry objects.
83
+ def member_entries(entry)
84
+ entries = []
85
+ dns = member_dns(entry)
86
+ uids = member_uids(entry)
87
+
88
+ entries.concat entries_by_uid(uids) unless uids.empty?
89
+ entries.concat entries_by_dn(dns) unless dns.empty?
90
+
91
+ entries
92
+ end
93
+ private :member_entries
94
+
95
+ # Internal: Bind a list of DNs to their respective entries.
96
+ #
97
+ # Returns an Array of Net::LDAP::Entry objects.
98
+ def entries_by_dn(members)
99
+ members.map do |dn|
100
+ ldap.domain(dn).bind(attributes: attrs)
101
+ end.compact
102
+ end
103
+ private :entries_by_dn
104
+
105
+ # Internal: Fetch entries by UID.
106
+ #
107
+ # Returns an Array of Net::LDAP::Entry objects.
108
+ def entries_by_uid(members)
109
+ filter = members.map { |uid| Net::LDAP::Filter.eq(ldap.uid, uid) }.reduce(:|)
110
+ domains.each_with_object([]) do |domain, entries|
111
+ entries.concat domain.search(filter: filter, attributes: attrs)
112
+ end.compact
113
+ end
114
+ private :entries_by_uid
115
+
116
+ # Internal: Returns an Array of String DNs for `groupOfNames` and
117
+ # `uniqueGroupOfNames` members.
118
+ def member_dns(entry)
119
+ MEMBERSHIP_NAMES.each_with_object([]) do |attr_name, members|
120
+ members.concat entry[attr_name]
121
+ end
122
+ end
123
+ private :member_dns
124
+
125
+ # Internal: Returns an Array of String UIDs for PosixGroups members.
126
+ def member_uids(entry)
127
+ entry["memberUid"]
128
+ end
129
+ private :member_uids
130
+ end
131
+ end
132
+ end
133
+ end
@@ -1,26 +1,4 @@
1
1
  require 'github/ldap/membership_validators/base'
2
- require 'github/ldap/membership_validators/detect'
3
2
  require 'github/ldap/membership_validators/classic'
4
3
  require 'github/ldap/membership_validators/recursive'
5
4
  require 'github/ldap/membership_validators/active_directory'
6
-
7
- module GitHub
8
- class Ldap
9
- # Provides various strategies for validating membership.
10
- #
11
- # For example:
12
- #
13
- # groups = domain.groups(%w(Engineering))
14
- # validator = GitHub::Ldap::MembershipValidators::Classic.new(ldap, groups)
15
- # validator.perform(entry) #=> true
16
- #
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
25
- end
26
- end
data/test/ldap_test.rb CHANGED
@@ -73,28 +73,45 @@ module GitHubLdapTestCases
73
73
  assert_equal "dc=github,dc=com", payload[:base]
74
74
  end
75
75
 
76
- def test_membership_validator_default
77
- assert_equal :detect, @ldap.membership_validator
76
+ def test_search_strategy_defaults
77
+ assert_equal GitHub::Ldap::MembershipValidators::Recursive, @ldap.membership_validator
78
+ assert_equal GitHub::Ldap::MemberSearch::Recursive, @ldap.member_search_strategy
78
79
  end
79
80
 
80
- def test_membership_validator_configured_to_classic_strategy
81
- @ldap.configure_membership_validation_strategy :classic
82
- assert_equal :classic, @ldap.membership_validator
81
+ def test_search_strategy_detects_active_directory
82
+ caps = Net::LDAP::Entry.new
83
+ caps[:supportedcapabilities] = [GitHub::Ldap::ACTIVE_DIRECTORY_V61_R2_OID]
84
+
85
+ @ldap.stub :capabilities, caps do
86
+ @ldap.configure_search_strategy :detect
87
+
88
+ assert_equal GitHub::Ldap::MembershipValidators::ActiveDirectory, @ldap.membership_validator
89
+ assert_equal GitHub::Ldap::MemberSearch::ActiveDirectory, @ldap.member_search_strategy
90
+ end
91
+ end
92
+
93
+ def test_search_strategy_configured_to_classic
94
+ @ldap.configure_search_strategy :classic
95
+ assert_equal GitHub::Ldap::MembershipValidators::Classic, @ldap.membership_validator
96
+ assert_equal GitHub::Ldap::MemberSearch::Classic, @ldap.member_search_strategy
83
97
  end
84
98
 
85
- def test_membership_validator_configured_to_recursive_strategy
86
- @ldap.configure_membership_validation_strategy :recursive
87
- assert_equal :recursive, @ldap.membership_validator
99
+ def test_search_strategy_configured_to_recursive
100
+ @ldap.configure_search_strategy :recursive
101
+ assert_equal GitHub::Ldap::MembershipValidators::Recursive, @ldap.membership_validator
102
+ assert_equal GitHub::Ldap::MemberSearch::Recursive, @ldap.member_search_strategy
88
103
  end
89
104
 
90
- def test_membership_validator_configured_to_active_directory_strategy
91
- @ldap.configure_membership_validation_strategy :active_directory
92
- assert_equal :active_directory, @ldap.membership_validator
105
+ def test_search_strategy_configured_to_active_directory
106
+ @ldap.configure_search_strategy :active_directory
107
+ assert_equal GitHub::Ldap::MembershipValidators::ActiveDirectory, @ldap.membership_validator
108
+ assert_equal GitHub::Ldap::MemberSearch::ActiveDirectory, @ldap.member_search_strategy
93
109
  end
94
110
 
95
- def test_membership_validator_misconfigured_to_unrecognized_strategy_falls_back_to_default
96
- @ldap.configure_membership_validation_strategy :unknown
97
- assert_equal :detect, @ldap.membership_validator
111
+ def test_search_strategy_misconfigured_to_unrecognized_strategy_falls_back_to_default
112
+ @ldap.configure_search_strategy :unknown
113
+ assert_equal GitHub::Ldap::MembershipValidators::Recursive, @ldap.membership_validator
114
+ assert_equal GitHub::Ldap::MemberSearch::Recursive, @ldap.member_search_strategy
98
115
  end
99
116
 
100
117
  def test_capabilities
@@ -0,0 +1,77 @@
1
+ require_relative '../test_helper'
2
+
3
+ class GitHubLdapActiveDirectoryMemberSearchStubbedTest < GitHub::Ldap::Test
4
+ # Only run when AD integration tests aren't run
5
+ def run(*)
6
+ self.class.test_env != "activedirectory" ? super : self
7
+ end
8
+
9
+ def find_group(cn)
10
+ @domain.groups([cn]).first
11
+ end
12
+
13
+ def setup
14
+ @ldap = GitHub::Ldap.new(options.merge(search_domains: %w(dc=github,dc=com)))
15
+ @domain = @ldap.domain("dc=github,dc=com")
16
+ @entry = @domain.user?('user1')
17
+ @strategy = GitHub::Ldap::MemberSearch::ActiveDirectory.new(@ldap)
18
+ end
19
+
20
+ def test_finds_group_members
21
+ members =
22
+ @ldap.stub :search, [@entry] do
23
+ @strategy.perform(find_group("nested-group1")).map(&:dn)
24
+ end
25
+ assert_includes members, @entry.dn
26
+ end
27
+
28
+ def test_finds_nested_group_members
29
+ members =
30
+ @ldap.stub :search, [@entry] do
31
+ @strategy.perform(find_group("n-depth-nested-group1")).map(&:dn)
32
+ end
33
+ assert_includes members, @entry.dn
34
+ end
35
+
36
+ def test_finds_deeply_nested_group_members
37
+ members =
38
+ @ldap.stub :search, [@entry] do
39
+ @strategy.perform(find_group("n-depth-nested-group9")).map(&:dn)
40
+ end
41
+ assert_includes members, @entry.dn
42
+ end
43
+ end
44
+
45
+ # See test/support/vm/activedirectory/README.md for details
46
+ class GitHubLdapActiveDirectoryMemberSearchIntegrationTest < GitHub::Ldap::Test
47
+ # Only run this test suite if ActiveDirectory is configured
48
+ def run(*)
49
+ self.class.test_env == "activedirectory" ? super : self
50
+ end
51
+
52
+ def find_group(cn)
53
+ @domain.groups([cn]).first
54
+ end
55
+
56
+ def setup
57
+ @ldap = GitHub::Ldap.new(options)
58
+ @domain = @ldap.domain(options[:search_domains])
59
+ @entry = @domain.user?('user1')
60
+ @strategy = GitHub::Ldap::MemberSearch::ActiveDirectory.new(@ldap)
61
+ end
62
+
63
+ def test_finds_group_members
64
+ members = @strategy.perform(find_group("nested-group1")).map(&:dn)
65
+ assert_includes members, @entry.dn
66
+ end
67
+
68
+ def test_finds_nested_group_members
69
+ members = @strategy.perform(find_group("n-depth-nested-group1")).map(&:dn)
70
+ assert_includes members, @entry.dn
71
+ end
72
+
73
+ def test_finds_deeply_nested_group_members
74
+ members = @strategy.perform(find_group("n-depth-nested-group9")).map(&:dn)
75
+ assert_includes members, @entry.dn
76
+ end
77
+ end
@@ -0,0 +1,40 @@
1
+ require_relative '../test_helper'
2
+
3
+ class GitHubLdapRecursiveMemberSearchTest < GitHub::Ldap::Test
4
+ def setup
5
+ @ldap = GitHub::Ldap.new(options.merge(search_domains: %w(dc=github,dc=com)))
6
+ @domain = @ldap.domain("dc=github,dc=com")
7
+ @entry = @domain.user?('user1')
8
+ @strategy = GitHub::Ldap::MemberSearch::Classic.new(@ldap)
9
+ end
10
+
11
+ def find_group(cn)
12
+ @domain.groups([cn]).first
13
+ end
14
+
15
+ def test_finds_group_members
16
+ members = @strategy.perform(find_group("nested-group1")).map(&:dn)
17
+ assert_includes members, @entry.dn
18
+ end
19
+
20
+ def test_finds_nested_group_members
21
+ members = @strategy.perform(find_group("n-depth-nested-group1")).map(&:dn)
22
+ assert_includes members, @entry.dn
23
+ end
24
+
25
+ def test_finds_deeply_nested_group_members
26
+ members = @strategy.perform(find_group("n-depth-nested-group9")).map(&:dn)
27
+ assert_includes members, @entry.dn
28
+ end
29
+
30
+ def test_finds_posix_group_members
31
+ members = @strategy.perform(find_group("posix-group1")).map(&:dn)
32
+ assert_includes members, @entry.dn
33
+ end
34
+
35
+ def test_does_not_respect_configured_depth_limit
36
+ strategy = GitHub::Ldap::MemberSearch::Classic.new(@ldap, depth: 2)
37
+ members = strategy.perform(find_group("n-depth-nested-group9")).map(&:dn)
38
+ assert_includes members, @entry.dn
39
+ end
40
+ end
@@ -0,0 +1,40 @@
1
+ require_relative '../test_helper'
2
+
3
+ class GitHubLdapRecursiveMemberSearchTest < GitHub::Ldap::Test
4
+ def setup
5
+ @ldap = GitHub::Ldap.new(options.merge(search_domains: %w(dc=github,dc=com)))
6
+ @domain = @ldap.domain("dc=github,dc=com")
7
+ @entry = @domain.user?('user1')
8
+ @strategy = GitHub::Ldap::MemberSearch::Recursive.new(@ldap)
9
+ end
10
+
11
+ def find_group(cn)
12
+ @domain.groups([cn]).first
13
+ end
14
+
15
+ def test_finds_group_members
16
+ members = @strategy.perform(find_group("nested-group1")).map(&:dn)
17
+ assert_includes members, @entry.dn
18
+ end
19
+
20
+ def test_finds_nested_group_members
21
+ members = @strategy.perform(find_group("n-depth-nested-group1")).map(&:dn)
22
+ assert_includes members, @entry.dn
23
+ end
24
+
25
+ def test_finds_deeply_nested_group_members
26
+ members = @strategy.perform(find_group("n-depth-nested-group9")).map(&:dn)
27
+ assert_includes members, @entry.dn
28
+ end
29
+
30
+ def test_finds_posix_group_members
31
+ members = @strategy.perform(find_group("posix-group1")).map(&:dn)
32
+ assert_includes members, @entry.dn
33
+ end
34
+
35
+ def test_respects_configured_depth_limit
36
+ strategy = GitHub::Ldap::MemberSearch::Recursive.new(@ldap, depth: 2)
37
+ members = strategy.perform(find_group("n-depth-nested-group9")).map(&:dn)
38
+ refute_includes members, @entry.dn
39
+ end
40
+ end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: github-ldap
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.0
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Calavera
8
+ - Matt Todd
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2014-11-15 00:00:00.000000000 Z
12
+ date: 2014-12-06 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: net-ldap
@@ -80,9 +81,10 @@ dependencies:
80
81
  - - ">="
81
82
  - !ruby/object:Gem::Version
82
83
  version: '0'
83
- description: Ldap authentication for humans
84
+ description: LDAP authentication for humans
84
85
  email:
85
86
  - david.calavera@gmail.com
87
+ - chiology@gmail.com
86
88
  executables: []
87
89
  extensions: []
88
90
  extra_rdoc_files: []
@@ -101,11 +103,15 @@ files:
101
103
  - lib/github/ldap/fixtures.ldif
102
104
  - lib/github/ldap/group.rb
103
105
  - lib/github/ldap/instrumentation.rb
106
+ - lib/github/ldap/member_search.rb
107
+ - lib/github/ldap/member_search/active_directory.rb
108
+ - lib/github/ldap/member_search/base.rb
109
+ - lib/github/ldap/member_search/classic.rb
110
+ - lib/github/ldap/member_search/recursive.rb
104
111
  - lib/github/ldap/membership_validators.rb
105
112
  - lib/github/ldap/membership_validators/active_directory.rb
106
113
  - lib/github/ldap/membership_validators/base.rb
107
114
  - lib/github/ldap/membership_validators/classic.rb
108
- - lib/github/ldap/membership_validators/detect.rb
109
115
  - lib/github/ldap/membership_validators/recursive.rb
110
116
  - lib/github/ldap/posix_group.rb
111
117
  - lib/github/ldap/server.rb
@@ -125,9 +131,11 @@ files:
125
131
  - test/fixtures/posixGroup.schema.ldif
126
132
  - test/group_test.rb
127
133
  - test/ldap_test.rb
134
+ - test/member_search/active_directory_test.rb
135
+ - test/member_search/classic_test.rb
136
+ - test/member_search/recursive_test.rb
128
137
  - test/membership_validators/active_directory_test.rb
129
138
  - test/membership_validators/classic_test.rb
130
- - test/membership_validators/detect_test.rb
131
139
  - test/membership_validators/recursive_test.rb
132
140
  - test/posix_group_test.rb
133
141
  - test/support/vm/activedirectory/.gitignore
@@ -161,7 +169,7 @@ rubyforge_project:
161
169
  rubygems_version: 2.2.2
162
170
  signing_key:
163
171
  specification_version: 4
164
- summary: Ldap client authentication wrapper without all the boilerplate
172
+ summary: LDAP client authentication wrapper without all the boilerplate
165
173
  test_files:
166
174
  - test/domain_test.rb
167
175
  - test/filter_test.rb
@@ -171,9 +179,11 @@ test_files:
171
179
  - test/fixtures/posixGroup.schema.ldif
172
180
  - test/group_test.rb
173
181
  - test/ldap_test.rb
182
+ - test/member_search/active_directory_test.rb
183
+ - test/member_search/classic_test.rb
184
+ - test/member_search/recursive_test.rb
174
185
  - test/membership_validators/active_directory_test.rb
175
186
  - test/membership_validators/classic_test.rb
176
- - test/membership_validators/detect_test.rb
177
187
  - test/membership_validators/recursive_test.rb
178
188
  - test/posix_group_test.rb
179
189
  - test/support/vm/activedirectory/.gitignore
@@ -1,69 +0,0 @@
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
@@ -1,49 +0,0 @@
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