ldaptic 0.2.0 → 0.2.1
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.
- data/LICENSE +1 -1
- data/README.rdoc +7 -16
- data/lib/ldaptic/adapters/active_directory_adapter.rb +9 -5
- data/lib/ldaptic/adapters/active_directory_ext.rb +2 -0
- data/lib/ldaptic/adapters/ldap_conn_adapter.rb +0 -6
- data/lib/ldaptic/attribute_set.rb +52 -17
- data/lib/ldaptic/dn.rb +10 -6
- data/lib/ldaptic/entry.rb +46 -17
- data/lib/ldaptic/filter.rb +1 -1
- data/lib/ldaptic/matching_rules.rb +80 -0
- data/lib/ldaptic/methods.rb +10 -7
- data/lib/ldaptic/schema.rb +6 -0
- data/test/ldaptic_adapters_test.rb +1 -1
- data/test/ldaptic_attribute_set_test.rb +1 -0
- data/test/ldaptic_dn_test.rb +5 -0
- data/test/ldaptic_hierarchy_test.rb +1 -0
- data/test/ldaptic_matching_rules_test.rb +38 -0
- metadata +11 -8
- data/lib/ldaptic/active_model.rb +0 -37
- data/lib/ldaptic/railtie.rb +0 -9
- data/test/core.schema +0 -582
- data/test/rbslapd1.rb +0 -111
- data/test/rbslapd4.rb +0 -172
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -29,18 +29,15 @@ a dynamically created module into a namespace of your choosing.
|
|
29
29
|
)
|
30
30
|
end
|
31
31
|
|
32
|
-
The adapter
|
33
|
-
|
34
|
-
|
35
|
-
instead). If the base is omitted, it will use the first naming context on the
|
36
|
-
server (usually what you want).
|
32
|
+
The adapter can usually be omitted as it defaults to :ldap_conn or :net_ldap,
|
33
|
+
based on which of the above two gems can be found. If the base is omitted, it
|
34
|
+
will use the first naming context on the server (usually what you want).
|
37
35
|
|
38
36
|
Entries are retrieved using the search method. Named parameters include
|
39
|
-
:base, :
|
37
|
+
:base, :scope, :filter, :attributes, :scope, and :limit. All are optional.
|
40
38
|
|
41
39
|
entries = Example.search(
|
42
|
-
:filter => {:objectClass => 'inetOrgPerson'}
|
43
|
-
:sort => :cn,
|
40
|
+
:filter => {:objectClass => 'inetOrgPerson'},
|
44
41
|
:limit => 10
|
45
42
|
)
|
46
43
|
|
@@ -61,7 +58,7 @@ Predictably, entries have attribute readers and writers.
|
|
61
58
|
=> <["root"]>
|
62
59
|
>> entry[:cn] = "admin"
|
63
60
|
|
64
|
-
The returned object is an attribute set and
|
61
|
+
The returned object is an attribute set and is similar to an array. Some
|
65
62
|
attributes are marked by the server as "single value;" those will return the
|
66
63
|
first element on method access but an attribute set on indexing access, for
|
67
64
|
programmatic convenience.
|
@@ -80,7 +77,7 @@ The indexing syntax can also be used to create and fetch children.
|
|
80
77
|
>> users[:cn=>'admin']
|
81
78
|
=> #<Example::InetOrgPerson cn=admin,ou=Users,dc=example,dc=com ...>
|
82
79
|
|
83
|
-
|
80
|
+
Entry also implements many of the standard methods you've come to expect in
|
84
81
|
an Active Record world (save, valid?, errors, to_param, attributes, ...).
|
85
82
|
In fact, it is fully Active Model compliant.
|
86
83
|
|
@@ -89,10 +86,6 @@ methods like search), Ldaptic::Entry, and Ldaptic::AttributeSet.
|
|
89
86
|
|
90
87
|
== To Do
|
91
88
|
|
92
|
-
* Verify everything still works. The tests take care of much of this, but
|
93
|
-
there are no integration tests for the adapters. In particular, I have no
|
94
|
-
way to verify the Active Directory adapter still works.
|
95
|
-
|
96
89
|
* The test suite (reflecting my fledgling testing abilities from 2007) is more
|
97
90
|
smoke test than BDD. Perhaps switch to RSpec in the quest to rectify this.
|
98
91
|
|
@@ -100,5 +93,3 @@ methods like search), Ldaptic::Entry, and Ldaptic::AttributeSet.
|
|
100
93
|
Record") are in the GitHub issue tracker. Vote for and comment on the ones
|
101
94
|
you would find useful, as most are on hold until someone has a real use
|
102
95
|
case.
|
103
|
-
|
104
|
-
* Pick a better name? Ldaptic has an ambiguous spelling.
|
@@ -3,6 +3,11 @@ require 'ldaptic/adapters/active_directory_ext'
|
|
3
3
|
|
4
4
|
module Ldaptic
|
5
5
|
module Adapters
|
6
|
+
# Before using this adapter, try the :ldap_conn adapter. The notes below
|
7
|
+
# were originally thought to apply to all Active Directory servers, but now
|
8
|
+
# I suspect they are peculiarities of a former employer's setup. This
|
9
|
+
# adapter is a candidate for removal.
|
10
|
+
#
|
6
11
|
# ActiveDirectoryAdapter is a LDAPConnAdapter with some Active Directory
|
7
12
|
# specific behaviors. To help mitigate server timeout issues, this adapter
|
8
13
|
# binds on each request and unbinds afterwards. For search requests, the
|
@@ -46,11 +51,10 @@ module Ldaptic
|
|
46
51
|
username = [@options[:domain], username].join("\\")
|
47
52
|
else
|
48
53
|
conn = new_connection(3268)
|
49
|
-
dn = conn.search2("", 0, "(objectClass=*", ['defaultNamingContext']).first['defaultNamingContext']
|
54
|
+
dn = conn.search2("", 0, "(objectClass=*)", ['defaultNamingContext']).first['defaultNamingContext']
|
50
55
|
if dn
|
51
|
-
domain = Ldaptic::DN(dn).
|
52
|
-
|
53
|
-
username = [username, domain.join(".")].join("@")
|
56
|
+
if domain = Ldaptic::DN(dn).domain
|
57
|
+
username = [username, domain].join('@')
|
54
58
|
end
|
55
59
|
end
|
56
60
|
end
|
@@ -70,7 +74,7 @@ module Ldaptic
|
|
70
74
|
end
|
71
75
|
|
72
76
|
def with_writer(&block)
|
73
|
-
with_port(389, &block)
|
77
|
+
with_port(@options[:port] || 389, &block)
|
74
78
|
end
|
75
79
|
|
76
80
|
end
|
@@ -153,10 +153,6 @@ module Ldaptic
|
|
153
153
|
end
|
154
154
|
|
155
155
|
def search_parameters(options = {})
|
156
|
-
case options[:sort]
|
157
|
-
when Proc, Method then s_attr, s_proc = nil, options[:sort]
|
158
|
-
else s_attr, s_proc = options[:sort], nil
|
159
|
-
end
|
160
156
|
[
|
161
157
|
options[:base],
|
162
158
|
options[:scope],
|
@@ -165,8 +161,6 @@ module Ldaptic
|
|
165
161
|
options[:attributes_only],
|
166
162
|
options[:timeout].to_i,
|
167
163
|
((options[:timeout].to_f % 1) * 1e6).round,
|
168
|
-
s_attr.to_s,
|
169
|
-
s_proc
|
170
164
|
]
|
171
165
|
end
|
172
166
|
|
@@ -31,9 +31,9 @@ module Ldaptic
|
|
31
31
|
@syntax = @entry.namespace.attribute_syntax(@name)
|
32
32
|
@target = target
|
33
33
|
if @type.nil?
|
34
|
-
@entry.logger "Unknown type for attribute #@name"
|
34
|
+
@entry.logger.warn "Unknown type for attribute #@name"
|
35
35
|
elsif @syntax.nil?
|
36
|
-
@entry.logger "Unknown syntax #{@type.syntax_oid} for attribute #{@name}"
|
36
|
+
@entry.logger.warn "Unknown syntax #{@type.syntax_oid} for attribute #{@name}"
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
@@ -77,15 +77,37 @@ module Ldaptic
|
|
77
77
|
@target.empty?
|
78
78
|
end
|
79
79
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
80
|
+
def index(*args, &block)
|
81
|
+
if block_given? || args.size != 1
|
82
|
+
return to_a.index(*args, &block)
|
83
|
+
else
|
84
|
+
target = matchable(args.first)
|
85
|
+
@target.each_with_index do |candidate, index|
|
86
|
+
return index if matchable(candidate) == target
|
87
|
+
end
|
87
88
|
end
|
88
|
-
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
alias find_index index
|
92
|
+
alias rindex index
|
93
|
+
|
94
|
+
def include?(target)
|
95
|
+
!!index(target)
|
96
|
+
end
|
97
|
+
|
98
|
+
def exclude?(target)
|
99
|
+
!index(target)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Like #include?, but asks the server rather than checking locally.
|
103
|
+
def compare(target)
|
104
|
+
@entry.compare(@name, target)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Adds the given attributes, discarding duplicates. All arrays are
|
108
|
+
# flattened.
|
109
|
+
def add(*attributes)
|
110
|
+
replace(@target + safe_array(attributes))
|
89
111
|
end
|
90
112
|
alias << add
|
91
113
|
alias concat add
|
@@ -102,7 +124,16 @@ module Ldaptic
|
|
102
124
|
def replace(*attributes)
|
103
125
|
attributes = safe_array(attributes)
|
104
126
|
user_modification_guard
|
105
|
-
|
127
|
+
seen = {}
|
128
|
+
filtered = []
|
129
|
+
attributes.each do |value|
|
130
|
+
matchable = matchable(value)
|
131
|
+
unless seen[matchable]
|
132
|
+
filtered << value
|
133
|
+
seen[matchable] = true
|
134
|
+
end
|
135
|
+
end
|
136
|
+
@target.replace(filtered)
|
106
137
|
self
|
107
138
|
end
|
108
139
|
|
@@ -119,16 +150,13 @@ module Ldaptic
|
|
119
150
|
|
120
151
|
# Remove the given attributes given, functioning more or less like
|
121
152
|
# Array#delete, except accepting multiple arguments.
|
122
|
-
#
|
123
|
-
# Two passes are made to find each element, one case sensitive and one
|
124
|
-
# ignoring case, before giving up.
|
125
153
|
def delete(*attributes, &block)
|
126
154
|
return clear if attributes.flatten.empty?
|
127
155
|
dest = @target.dup
|
128
156
|
ret = []
|
129
157
|
safe_array(attributes).each do |attribute|
|
130
158
|
ret << dest.delete(attribute) do
|
131
|
-
match = dest.detect {|x| x
|
159
|
+
match = dest.detect {|x| matchable(x) == matchable(attribute)}
|
132
160
|
if match
|
133
161
|
dest.delete(match)
|
134
162
|
else
|
@@ -158,8 +186,7 @@ module Ldaptic
|
|
158
186
|
alias map! collect!
|
159
187
|
|
160
188
|
def insert(index, *objects)
|
161
|
-
|
162
|
-
@target.insert(index, *safe_array(objects))
|
189
|
+
replace(@target.dup.insert(index, *safe_array(objects)))
|
163
190
|
self
|
164
191
|
end
|
165
192
|
|
@@ -261,6 +288,14 @@ module Ldaptic
|
|
261
288
|
end
|
262
289
|
end
|
263
290
|
|
291
|
+
def matchable(value)
|
292
|
+
if @type
|
293
|
+
@type.matchable(value)
|
294
|
+
else
|
295
|
+
format(value)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
264
299
|
def safe_array(attributes)
|
265
300
|
Array(attributes).flatten.compact.map {|x| format(x)}
|
266
301
|
end
|
data/lib/ldaptic/dn.rb
CHANGED
@@ -75,13 +75,13 @@ module Ldaptic
|
|
75
75
|
filter = "(objectClass=*)"
|
76
76
|
if source.respond_to?(:search2_ext)
|
77
77
|
source.search2(
|
78
|
-
|
78
|
+
to_s,
|
79
79
|
scope,
|
80
80
|
filter
|
81
81
|
)
|
82
82
|
elsif source.respond_to?(:search)
|
83
83
|
Array(source.search(
|
84
|
-
:base =>
|
84
|
+
:base => to_s,
|
85
85
|
:scope => scope,
|
86
86
|
:filter => filter,
|
87
87
|
:limit => 1
|
@@ -113,6 +113,12 @@ module Ldaptic
|
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
|
+
# Join all DC elements with periods.
|
117
|
+
def domain
|
118
|
+
components = rdns.map {|rdn| rdn[:dc]}.compact
|
119
|
+
components.join('.') unless components.empty?
|
120
|
+
end
|
121
|
+
|
116
122
|
def parent
|
117
123
|
Ldaptic::DN(rdns[1..-1], source)
|
118
124
|
end
|
@@ -142,12 +148,10 @@ module Ldaptic
|
|
142
148
|
end
|
143
149
|
end
|
144
150
|
if other.kind_of?(Ldaptic::DN)
|
145
|
-
|
151
|
+
rdns == other.rdns
|
146
152
|
else
|
147
153
|
super
|
148
154
|
end
|
149
|
-
# rescue
|
150
|
-
# super
|
151
155
|
end
|
152
156
|
|
153
157
|
# Pass in one or more hashes to augment the DN. Otherwise, this behaves
|
@@ -338,7 +342,7 @@ module Ldaptic
|
|
338
342
|
end
|
339
343
|
|
340
344
|
def merge(hash)
|
341
|
-
|
345
|
+
dup.update(hash)
|
342
346
|
end
|
343
347
|
|
344
348
|
def delete(key)
|
data/lib/ldaptic/entry.rb
CHANGED
@@ -48,11 +48,18 @@ module Ldaptic
|
|
48
48
|
def create_accessors #:nodoc:
|
49
49
|
to_be_evaled = ""
|
50
50
|
(may(false) + must(false)).each do |attr|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
if type = namespace.attribute_type(attr)
|
52
|
+
names = type.names
|
53
|
+
else
|
54
|
+
names = [attr]
|
55
|
+
end
|
56
|
+
names.each do |name|
|
57
|
+
method = name.to_s.tr_s('-_', '_-')
|
58
|
+
to_be_evaled << <<-RUBY
|
59
|
+
def #{method}() read_attribute('#{attr}').one end
|
60
|
+
def #{method}=(value) write_attribute('#{attr}', value) end
|
61
|
+
RUBY
|
62
|
+
end
|
56
63
|
end
|
57
64
|
class_eval(to_be_evaled, __FILE__, __LINE__)
|
58
65
|
end
|
@@ -176,7 +183,7 @@ module Ldaptic
|
|
176
183
|
@attributes = {}
|
177
184
|
data = data.dup
|
178
185
|
if dn = data.delete('dn') || data.delete(:dn)
|
179
|
-
dn.first if dn.kind_of?(Array)
|
186
|
+
dn = dn.first if dn.kind_of?(Array)
|
180
187
|
self.dn = dn
|
181
188
|
end
|
182
189
|
merge_attributes(data)
|
@@ -273,7 +280,11 @@ module Ldaptic
|
|
273
280
|
# #method_missing delegates to this method, method names with underscores
|
274
281
|
# map to attributes with hyphens.
|
275
282
|
def read_attribute(key)
|
276
|
-
|
283
|
+
if type = namespace.attribute_type(key)
|
284
|
+
key = type.names.first
|
285
|
+
else
|
286
|
+
key = Ldaptic.encode(key)
|
287
|
+
end
|
277
288
|
@attributes[key] ||= ((@original_attributes || {}).fetch(key, [])).dup
|
278
289
|
Ldaptic::AttributeSet.new(self, key, @attributes[key])
|
279
290
|
end
|
@@ -302,7 +313,7 @@ module Ldaptic
|
|
302
313
|
# Changes are not committed to the server until #save is called.
|
303
314
|
def write_attribute(key, values)
|
304
315
|
set = read_attribute(key)
|
305
|
-
if values.respond_to?(:to_str) && set.syntax_object && set.syntax_object.error("1\n1")
|
316
|
+
if values.respond_to?(:to_str) && !set.single_value? && set.syntax_object && set.syntax_object.error("1\n1")
|
306
317
|
values = values.split(/\r?\n/)
|
307
318
|
elsif values == ''
|
308
319
|
values = []
|
@@ -357,7 +368,6 @@ module Ldaptic
|
|
357
368
|
namespace.compare(dn, key, value)
|
358
369
|
end
|
359
370
|
|
360
|
-
# attr_reader :attributes
|
361
371
|
def attribute_names
|
362
372
|
attributes.keys
|
363
373
|
end
|
@@ -370,25 +380,41 @@ module Ldaptic
|
|
370
380
|
self['objectClass'].map {|c| namespace.object_class(c)} - self.class.ldap_ancestors
|
371
381
|
end
|
372
382
|
|
373
|
-
def must(
|
374
|
-
self.class.must
|
383
|
+
def must(include_aliases = false)
|
384
|
+
attrs = (self.class.must + aux.map {|a| a.must(false)}.flatten).uniq
|
385
|
+
if include_aliases
|
386
|
+
attrs.map do |attr|
|
387
|
+
type = namespace.attribute_type(attr)
|
388
|
+
type ? type.names : attr
|
389
|
+
end.flatten
|
390
|
+
else
|
391
|
+
attrs
|
392
|
+
end
|
375
393
|
end
|
376
394
|
|
377
|
-
def may(
|
378
|
-
self.class.may
|
395
|
+
def may(include_aliases = false)
|
396
|
+
attrs = (self.class.may + aux.map {|a| a.may(false)}.flatten).uniq - must
|
397
|
+
if include_aliases
|
398
|
+
attrs.map do |attr|
|
399
|
+
type = namespace.attribute_type(attr)
|
400
|
+
type ? type.names : attr
|
401
|
+
end.flatten
|
402
|
+
else
|
403
|
+
attrs
|
404
|
+
end
|
379
405
|
end
|
380
406
|
|
381
407
|
def may_must(attribute)
|
382
408
|
attribute = Ldaptic.encode(attribute)
|
383
|
-
if must.include?(attribute)
|
409
|
+
if must(true).include?(attribute)
|
384
410
|
:must
|
385
|
-
elsif may.include?(attribute)
|
411
|
+
elsif may(true).include?(attribute)
|
386
412
|
:may
|
387
413
|
end
|
388
414
|
end
|
389
415
|
|
390
416
|
def respond_to?(method, *) #:nodoc:
|
391
|
-
both = may + must
|
417
|
+
both = may(true) + must(true)
|
392
418
|
super || (both + both.map {|x| "#{x}="} + both.map {|x| "#{x}-before-type-cast"}).include?(Ldaptic.encode(method.to_sym))
|
393
419
|
end
|
394
420
|
|
@@ -435,7 +461,10 @@ module Ldaptic
|
|
435
461
|
alias find /
|
436
462
|
|
437
463
|
def fetch(dn = self.dn, options = {}) #:nodoc:
|
438
|
-
|
464
|
+
if dn.kind_of?(Hash)
|
465
|
+
dn = self.dn/dn
|
466
|
+
end
|
467
|
+
namespace.fetch(dn, options)
|
439
468
|
end
|
440
469
|
|
441
470
|
# If a Hash or a String containing "=" is given, the argument is treated as
|
data/lib/ldaptic/filter.rb
CHANGED
@@ -144,7 +144,7 @@ module Ldaptic
|
|
144
144
|
def process
|
145
145
|
"(#{@array*''})" if @array.compact.size > 1
|
146
146
|
end
|
147
|
-
def to_net_ldap_filter #:nodoc
|
147
|
+
def to_net_ldap_filter #:nodoc:
|
148
148
|
@array[1..-1].inject {|m, o| m.to_net_ldap_filter.send(@array.first, o.to_net_ldap_filter)}
|
149
149
|
end
|
150
150
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'ldaptic/syntaxes'
|
2
|
+
|
3
|
+
module Ldaptic
|
4
|
+
# RFC4517 - Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching Rules
|
5
|
+
# RFC4518 - Lightweight Directory Access Protocol (LDAP): Internationalized
|
6
|
+
# String Preparation
|
7
|
+
module MatchingRules
|
8
|
+
def self.for(name)
|
9
|
+
name = name.to_s
|
10
|
+
name = name[0..0].upcase + name[1..-1].to_s
|
11
|
+
if !name.empty? && const_defined?(name)
|
12
|
+
const_get(name)
|
13
|
+
else
|
14
|
+
CaseIgnoreMatch
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class OctetStringMatch
|
19
|
+
def matchable(value)
|
20
|
+
value
|
21
|
+
end
|
22
|
+
|
23
|
+
def match(one, two)
|
24
|
+
matchable(one) == matchable(two)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Boolean < OctetStringMatch
|
29
|
+
end
|
30
|
+
|
31
|
+
class CaseExactMatch < OctetStringMatch
|
32
|
+
def matchable(value)
|
33
|
+
super.gsub(/ +/, ' ').sub(/\A */, ' ').sub(/ *\z/, ' ')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class CaseExactIA5Match < CaseExactMatch
|
38
|
+
end
|
39
|
+
|
40
|
+
class CaseIgnoreMatch < CaseExactMatch
|
41
|
+
def matchable(value)
|
42
|
+
super.downcase
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class CaseIgnoreIA5Match < CaseIgnoreMatch
|
47
|
+
end
|
48
|
+
|
49
|
+
class CaseIgnoreListMatch < CaseIgnoreMatch
|
50
|
+
end
|
51
|
+
|
52
|
+
class GeneralizedTimeMatch < OctetStringMatch
|
53
|
+
def matchable(value)
|
54
|
+
Ldaptic::Syntaxes::GeneralizedTime.parse(value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class NumericStringMatch < OctetStringMatch
|
59
|
+
def matchable(value)
|
60
|
+
super.delete(' ')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class DistinguishedNameMatch < OctetStringMatch
|
65
|
+
def matchable(value)
|
66
|
+
Ldaptic::DN(value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class TelephoneNumberMatch < CaseIgnoreMatch
|
71
|
+
# Doesn't remove unicode hyphen equivalents \u058A, \u2010, \u2011,
|
72
|
+
# \u2212, \ufe63, or \uff0d on account of unicode being so darn difficult
|
73
|
+
# to get right in both 1.8 and 1.9.
|
74
|
+
def matchable(value)
|
75
|
+
super.delete(' ').delete('-')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|