github-ldap 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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