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 +4 -4
- data/CHANGELOG.md +6 -0
- data/github-ldap.gemspec +5 -5
- data/lib/github/ldap.rb +84 -22
- data/lib/github/ldap/domain.rb +5 -2
- data/lib/github/ldap/group.rb +6 -1
- data/lib/github/ldap/member_search.rb +4 -0
- data/lib/github/ldap/member_search/active_directory.rb +60 -0
- data/lib/github/ldap/member_search/base.rb +34 -0
- data/lib/github/ldap/member_search/classic.rb +18 -0
- data/lib/github/ldap/member_search/recursive.rb +133 -0
- data/lib/github/ldap/membership_validators.rb +0 -22
- data/test/ldap_test.rb +31 -14
- data/test/member_search/active_directory_test.rb +77 -0
- data/test/member_search/classic_test.rb +40 -0
- data/test/member_search/recursive_test.rb +40 -0
- metadata +17 -7
- data/lib/github/ldap/membership_validators/detect.rb +0 -69
- data/test/membership_validators/detect_test.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13b19164520b8ed69dc3defdfa76b747767d9006
|
4
|
+
data.tar.gz: f34f58a9d8d91a89b5f9873b7a17daf251b9ac3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
6
|
-
spec.authors = ["David Calavera"]
|
7
|
-
spec.email = ["david.calavera@gmail.com"]
|
8
|
-
spec.description = %q{
|
9
|
-
spec.summary = %q{
|
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
|
92
|
-
|
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
|
246
|
+
# Internal: Configure the member search and membership validation strategies.
|
247
|
+
#
|
248
|
+
# TODO: Inline the logic in these two methods here.
|
240
249
|
#
|
241
|
-
#
|
242
|
-
|
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
|
245
|
-
#
|
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
|
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"
|
252
|
-
|
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
|
-
|
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
|
data/lib/github/ldap/domain.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/github/ldap/group.rb
CHANGED
@@ -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,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
|
77
|
-
assert_equal
|
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
|
81
|
-
|
82
|
-
|
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
|
86
|
-
@ldap.
|
87
|
-
assert_equal
|
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
|
91
|
-
@ldap.
|
92
|
-
assert_equal
|
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
|
96
|
-
@ldap.
|
97
|
-
assert_equal
|
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.
|
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-
|
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:
|
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:
|
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
|