ldaptic 0.2.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.
- data/LICENSE +20 -0
- data/README.rdoc +104 -0
- data/Rakefile +41 -0
- data/lib/ldaptic.rb +151 -0
- data/lib/ldaptic/active_model.rb +37 -0
- data/lib/ldaptic/adapters.rb +90 -0
- data/lib/ldaptic/adapters/abstract_adapter.rb +123 -0
- data/lib/ldaptic/adapters/active_directory_adapter.rb +78 -0
- data/lib/ldaptic/adapters/active_directory_ext.rb +12 -0
- data/lib/ldaptic/adapters/ldap_conn_adapter.rb +262 -0
- data/lib/ldaptic/adapters/net_ldap_adapter.rb +173 -0
- data/lib/ldaptic/adapters/net_ldap_ext.rb +24 -0
- data/lib/ldaptic/attribute_set.rb +283 -0
- data/lib/ldaptic/dn.rb +365 -0
- data/lib/ldaptic/entry.rb +646 -0
- data/lib/ldaptic/error_set.rb +34 -0
- data/lib/ldaptic/errors.rb +136 -0
- data/lib/ldaptic/escape.rb +110 -0
- data/lib/ldaptic/filter.rb +282 -0
- data/lib/ldaptic/methods.rb +387 -0
- data/lib/ldaptic/railtie.rb +9 -0
- data/lib/ldaptic/schema.rb +246 -0
- data/lib/ldaptic/syntaxes.rb +319 -0
- data/test/core.schema +582 -0
- data/test/ldaptic_active_model_test.rb +40 -0
- data/test/ldaptic_adapters_test.rb +35 -0
- data/test/ldaptic_attribute_set_test.rb +57 -0
- data/test/ldaptic_dn_test.rb +110 -0
- data/test/ldaptic_entry_test.rb +22 -0
- data/test/ldaptic_errors_test.rb +23 -0
- data/test/ldaptic_escape_test.rb +47 -0
- data/test/ldaptic_filter_test.rb +53 -0
- data/test/ldaptic_hierarchy_test.rb +90 -0
- data/test/ldaptic_schema_test.rb +44 -0
- data/test/ldaptic_syntaxes_test.rb +66 -0
- data/test/mock_adapter.rb +47 -0
- data/test/rbslapd1.rb +111 -0
- data/test/rbslapd4.rb +172 -0
- data/test/test_helper.rb +2 -0
- metadata +146 -0
data/lib/ldaptic/dn.rb
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
require 'ldaptic/escape'
|
|
2
|
+
|
|
3
|
+
module Ldaptic
|
|
4
|
+
|
|
5
|
+
# Instantiate a new Ldaptic::DN object with the arguments given. Unlike
|
|
6
|
+
# Ldaptic::DN.new(dn), this method coerces the first argument to a string,
|
|
7
|
+
# unless it is already a string or an array. If the first argument is nil,
|
|
8
|
+
# nil is returned.
|
|
9
|
+
def self.DN(dn, source = nil)
|
|
10
|
+
return if dn.nil?
|
|
11
|
+
dn = dn.dn if dn.respond_to?(:dn)
|
|
12
|
+
if dn.kind_of?(::Ldaptic::DN)
|
|
13
|
+
if source
|
|
14
|
+
dn = dn.dup
|
|
15
|
+
dn.source = source
|
|
16
|
+
end
|
|
17
|
+
return dn
|
|
18
|
+
end
|
|
19
|
+
if dn.respond_to?(:to_hash)
|
|
20
|
+
dn = [dn]
|
|
21
|
+
elsif ! dn.respond_to?(:to_ary)
|
|
22
|
+
dn = dn.to_s
|
|
23
|
+
end
|
|
24
|
+
DN.new(dn, source)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# RFC4512 - Lightweight Directory Access Protocol (LDAP): Directory Information Models
|
|
28
|
+
# RFC4514 - Lightweight Directory Access Protocol (LDAP): String Representation of Distinguished Names
|
|
29
|
+
#
|
|
30
|
+
class DN < ::String
|
|
31
|
+
|
|
32
|
+
OID = '1.3.6.1.4.1.1466.115.121.1.12' unless defined? OID
|
|
33
|
+
# Ldaptic::DN[{:dc => 'com'}, {:dc => 'amazon'}]
|
|
34
|
+
# => "dc=amazon,dc=com"
|
|
35
|
+
def self.[](*args)
|
|
36
|
+
Ldaptic::DN(args.reverse)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_accessor :source
|
|
40
|
+
|
|
41
|
+
# Create a new Ldaptic::DN object. dn can either be a string, or an array
|
|
42
|
+
# of pairs.
|
|
43
|
+
#
|
|
44
|
+
# Ldaptic::DN([{:cn=>"Thomas, David"}, {:dc=>"pragprog"}, {:dc=>"com"}])
|
|
45
|
+
# # => "CN=Thomas\\, David,DC=pragprog,DC=com"
|
|
46
|
+
#
|
|
47
|
+
# The optional second object specifies either an LDAP::Conn object or a
|
|
48
|
+
# Ldaptic object to be used to find the DN with #find.
|
|
49
|
+
def initialize(dn, source = nil)
|
|
50
|
+
@source = source
|
|
51
|
+
dn = dn.dn if dn.respond_to?(:dn)
|
|
52
|
+
if dn.respond_to?(:to_ary)
|
|
53
|
+
dn = dn.map do |pair|
|
|
54
|
+
if pair.kind_of?(Hash)
|
|
55
|
+
Ldaptic::RDN(pair).to_str
|
|
56
|
+
else
|
|
57
|
+
pair
|
|
58
|
+
end
|
|
59
|
+
end * ','
|
|
60
|
+
end
|
|
61
|
+
if dn.include?(".") && !dn.include?("=")
|
|
62
|
+
dn = dn.split(".").map {|dc| "DC=#{Ldaptic.escape(dc)}"} * ","
|
|
63
|
+
end
|
|
64
|
+
super(dn)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def to_dn
|
|
68
|
+
self
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# If a source object was given, it is used to search for the DN.
|
|
72
|
+
# Otherwise, an exception is raised.
|
|
73
|
+
def find(source = @source)
|
|
74
|
+
scope = 0
|
|
75
|
+
filter = "(objectClass=*)"
|
|
76
|
+
if source.respond_to?(:search2_ext)
|
|
77
|
+
source.search2(
|
|
78
|
+
self.to_s,
|
|
79
|
+
scope,
|
|
80
|
+
filter
|
|
81
|
+
)
|
|
82
|
+
elsif source.respond_to?(:search)
|
|
83
|
+
Array(source.search(
|
|
84
|
+
:base => self.to_s,
|
|
85
|
+
:scope => scope,
|
|
86
|
+
:filter => filter,
|
|
87
|
+
:limit => 1
|
|
88
|
+
))
|
|
89
|
+
else
|
|
90
|
+
raise RuntimeError, "missing or invalid source for LDAP search", caller
|
|
91
|
+
end.first
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Convert the DN to an array of RDNs.
|
|
95
|
+
#
|
|
96
|
+
# Ldaptic::DN("cn=Thomas\\, David,dc=pragprog,dc=com").rdns
|
|
97
|
+
# # => [{:cn=>"Thomas, David"},{:dc=>"pragprog"},{:dc=>"com"}]
|
|
98
|
+
def rdns
|
|
99
|
+
rdn_strings.map {|rdn| RDN.new(rdn)}
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def rdn_strings
|
|
103
|
+
Ldaptic.split(self, ?,)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def to_a
|
|
107
|
+
# This is really horrid, but the last hack broke. Consider abandoning
|
|
108
|
+
# this method entirely.
|
|
109
|
+
if caller.first =~ /:in `Array'$/
|
|
110
|
+
[self]
|
|
111
|
+
else
|
|
112
|
+
rdns
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def parent
|
|
117
|
+
Ldaptic::DN(rdns[1..-1], source)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def rdn
|
|
121
|
+
rdns.first
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def normalize
|
|
125
|
+
Ldaptic::DN(rdns, source)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def normalize!
|
|
129
|
+
replace(normalize)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# TODO: investigate compliance with
|
|
133
|
+
# RFC4517 - Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching Rules
|
|
134
|
+
def ==(other)
|
|
135
|
+
if other.respond_to?(:dn)
|
|
136
|
+
other = Ldaptic::DN(other)
|
|
137
|
+
end
|
|
138
|
+
normalize = lambda do |hash|
|
|
139
|
+
hash.inject({}) do |m, (k, v)|
|
|
140
|
+
m[Ldaptic.encode(k).upcase] = v
|
|
141
|
+
m
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
if other.kind_of?(Ldaptic::DN)
|
|
145
|
+
self.rdns == other.rdns
|
|
146
|
+
else
|
|
147
|
+
super
|
|
148
|
+
end
|
|
149
|
+
# rescue
|
|
150
|
+
# super
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Pass in one or more hashes to augment the DN. Otherwise, this behaves
|
|
154
|
+
# the same as String#[]
|
|
155
|
+
|
|
156
|
+
def [](*args)
|
|
157
|
+
if args.first.kind_of?(Hash) || args.first.kind_of?(Ldaptic::DN)
|
|
158
|
+
send(:/, *args)
|
|
159
|
+
else
|
|
160
|
+
super
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Prepend an RDN to the DN.
|
|
165
|
+
#
|
|
166
|
+
# Ldaptic::DN(:dc => "com")/{:dc => "foobar"} #=> "DC=foobar,DC=com"
|
|
167
|
+
def /(*args)
|
|
168
|
+
Ldaptic::DN(args.reverse + rdns, source)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# With a Hash (and only with a Hash), prepends a RDN to the DN, modifying
|
|
172
|
+
# the receiver in place. Otherwise, behaves like String#<<.
|
|
173
|
+
def <<(arg)
|
|
174
|
+
if arg.kind_of?(Hash)
|
|
175
|
+
replace(self/arg)
|
|
176
|
+
else
|
|
177
|
+
super
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# With a Hash, check for the presence of an RDN. Otherwise, behaves like
|
|
182
|
+
# String#include?
|
|
183
|
+
def include?(arg)
|
|
184
|
+
if arg.kind_of?(Hash)
|
|
185
|
+
rdns.include?(arg)
|
|
186
|
+
else
|
|
187
|
+
super
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def self.RDN(rdn)
|
|
194
|
+
rdn = rdn.rdn if rdn.respond_to?(:rdn)
|
|
195
|
+
if rdn.respond_to?(:to_rdn)
|
|
196
|
+
rdn.to_rdn
|
|
197
|
+
else
|
|
198
|
+
RDN.new(rdn||{})
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class RDN < Hash
|
|
203
|
+
|
|
204
|
+
def self.parse_string(string) #:nodoc:
|
|
205
|
+
|
|
206
|
+
Ldaptic.split(string, ?+).inject({}) do |hash, pair|
|
|
207
|
+
k, v = Ldaptic.split(pair, ?=).map {|x| Ldaptic.unescape(x)}
|
|
208
|
+
hash[k.downcase.to_sym] = v
|
|
209
|
+
hash
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
rescue
|
|
213
|
+
raise RuntimeError, "error parsing RDN", caller
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def initialize(rdn = {})
|
|
217
|
+
rdn = rdn.rdn if rdn.respond_to?(:rdn)
|
|
218
|
+
if rdn.kind_of?(String)
|
|
219
|
+
rdn = RDN.parse_string(rdn)
|
|
220
|
+
end
|
|
221
|
+
if rdn.kind_of?(Hash)
|
|
222
|
+
super()
|
|
223
|
+
update(rdn)
|
|
224
|
+
else
|
|
225
|
+
raise TypeError, "default value #{rdn.inspect} not allowed", caller
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def /(*args)
|
|
230
|
+
Ldaptic::DN([self]).send(:/, *args)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def to_rdn
|
|
234
|
+
self
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def to_str
|
|
238
|
+
collect do |k, v|
|
|
239
|
+
"#{k.kind_of?(String) ? k : Ldaptic.encode(k).upcase}=#{Ldaptic.escape(v)}"
|
|
240
|
+
end.sort.join("+")
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
alias to_s to_str
|
|
244
|
+
|
|
245
|
+
def downcase!
|
|
246
|
+
values.each {|v| v.downcase!}
|
|
247
|
+
self
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def upcase!
|
|
251
|
+
values.each {|v| v.upcase!}
|
|
252
|
+
self
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def downcase() clone.downcase! end
|
|
256
|
+
def upcase() clone. upcase! end
|
|
257
|
+
|
|
258
|
+
unless defined? MANDATORY_ATTRIBUTE_TYPES
|
|
259
|
+
MANDATORY_ATTRIBUTE_TYPES = %w(CN L ST O OU C STREET DC UID)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
MANDATORY_ATTRIBUTE_TYPES.map {|a| a.downcase.to_sym }.each do |type|
|
|
263
|
+
define_method(type) { self[type] }
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def [](*args)
|
|
267
|
+
if args.size == 1
|
|
268
|
+
if args.first.respond_to?(:to_sym)
|
|
269
|
+
return super(convert_key(args.first))
|
|
270
|
+
elsif args.first.kind_of?(Hash)
|
|
271
|
+
return self/args.first
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
to_str[*args]
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def hash
|
|
278
|
+
to_str.downcase.hash
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def eql?(other)
|
|
282
|
+
if other.respond_to?(:to_str)
|
|
283
|
+
to_str.casecmp(other.to_str).zero?
|
|
284
|
+
elsif other.kind_of?(Hash)
|
|
285
|
+
eql?(Ldaptic::RDN(other)) rescue false
|
|
286
|
+
else
|
|
287
|
+
super
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
alias == eql?
|
|
292
|
+
|
|
293
|
+
def clone
|
|
294
|
+
inject(RDN.new) do |h, (k, v)|
|
|
295
|
+
h[k] = v.dup; h
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Net::LDAP compatibility
|
|
300
|
+
def to_ber #:nodoc:
|
|
301
|
+
to_str.to_ber
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# Based on ActiveSupport's HashWithIndifferentAccess
|
|
305
|
+
|
|
306
|
+
alias_method :regular_writer, '[]=' unless method_defined?(:regular_writer)
|
|
307
|
+
alias_method :regular_update, :update unless method_defined?(:regular_update)
|
|
308
|
+
|
|
309
|
+
def []=(key, value)
|
|
310
|
+
regular_writer(convert_key(key), convert_value(value))
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
def update(other_hash)
|
|
314
|
+
other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
|
|
315
|
+
self
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
alias_method :merge!, :update
|
|
319
|
+
|
|
320
|
+
def key?(key)
|
|
321
|
+
super(convert_key(key))
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
alias_method :include?, :key?
|
|
325
|
+
alias_method :has_key?, :key?
|
|
326
|
+
alias_method :member?, :key?
|
|
327
|
+
|
|
328
|
+
def fetch(key, *extras)
|
|
329
|
+
super(convert_key(key), *extras)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def values_at(*indices)
|
|
333
|
+
indices.collect {|key| self[convert_key(key)]}
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def dup
|
|
337
|
+
RDN.new(self)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def merge(hash)
|
|
341
|
+
self.dup.update(hash)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def delete(key)
|
|
345
|
+
super(convert_key(key))
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
private
|
|
349
|
+
|
|
350
|
+
def convert_key(key)
|
|
351
|
+
if key.respond_to?(:to_str)
|
|
352
|
+
key.to_str
|
|
353
|
+
elsif key.respond_to?(:to_sym)
|
|
354
|
+
key.to_sym.to_s
|
|
355
|
+
else
|
|
356
|
+
raise TypeError, "keys in an Ldaptic::RDN must be symbols", caller(1)
|
|
357
|
+
end.downcase.to_sym
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def convert_value(value)
|
|
361
|
+
value.to_s
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
end
|
|
365
|
+
end
|
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
require 'ldaptic/attribute_set'
|
|
2
|
+
require 'ldaptic/error_set'
|
|
3
|
+
|
|
4
|
+
module Ldaptic
|
|
5
|
+
|
|
6
|
+
# When a new Ldaptic namespace is created, a Ruby class hierarchy is
|
|
7
|
+
# contructed that mirrors the server's object classes. Ldaptic::Entry
|
|
8
|
+
# serves as the base class for this hierarchy.
|
|
9
|
+
class Entry
|
|
10
|
+
# Constructs a deep copy of a set of LDAP attributes, normalizing them to
|
|
11
|
+
# arrays as appropriate. The returned hash has a default value of [].
|
|
12
|
+
def self.clone_ldap_hash(attributes) #:nodoc:
|
|
13
|
+
hash = Hash.new
|
|
14
|
+
attributes.each do |k, v|
|
|
15
|
+
k = k.kind_of?(Symbol) ? k.to_s.tr('_', '-') : k.dup
|
|
16
|
+
hash[k] = Array(v).map {|x| x.dup rescue x}
|
|
17
|
+
end
|
|
18
|
+
hash
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# For Active Model compliance. Delegates to #namespace.
|
|
22
|
+
def self.model_name
|
|
23
|
+
namespace.model_name
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
attr_reader :oid, :desc, :sup
|
|
28
|
+
%w(obsolete abstract structural auxiliary).each do |attr|
|
|
29
|
+
class_eval("def #{attr}?; !! @#{attr}; end")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def logger
|
|
33
|
+
namespace.logger
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns an array of all names for the object class. Typically the
|
|
37
|
+
# number of names is one, but it is possible for an object class to have
|
|
38
|
+
# aliases.
|
|
39
|
+
def names
|
|
40
|
+
Array(@name)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def has_attribute?(attribute)
|
|
44
|
+
attribute = Ldaptic.encode(attribute)
|
|
45
|
+
may.include?(attribute) || must.include?(attribute)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def create_accessors #:nodoc:
|
|
49
|
+
to_be_evaled = ""
|
|
50
|
+
(may(false) + must(false)).each do |attr|
|
|
51
|
+
method = attr.to_s.tr_s('-_', '_-')
|
|
52
|
+
to_be_evaled << <<-RUBY
|
|
53
|
+
def #{method}() read_attribute('#{attr}').one end
|
|
54
|
+
def #{method}=(value) write_attribute('#{attr}', value) end
|
|
55
|
+
RUBY
|
|
56
|
+
end
|
|
57
|
+
class_eval(to_be_evaled, __FILE__, __LINE__)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# An array of classes that make up the inheritance hierarchy.
|
|
61
|
+
#
|
|
62
|
+
# L::OrganizationalPerson.ldap_ancestors #=> [L::OrganizationalPerson, L::Person, L::Top]
|
|
63
|
+
def ldap_ancestors
|
|
64
|
+
ancestors.select {|o| o.respond_to?(:oid) && o.oid }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
attr_reader :namespace
|
|
68
|
+
|
|
69
|
+
def may(all = true)
|
|
70
|
+
if all
|
|
71
|
+
core = []
|
|
72
|
+
nott = []
|
|
73
|
+
ldap_ancestors.reverse.each do |klass|
|
|
74
|
+
core |= Array(klass.may(false))
|
|
75
|
+
nott |= Array(klass.must(false))
|
|
76
|
+
end
|
|
77
|
+
if dit = dit_content_rule
|
|
78
|
+
core.push(*Array(dit.may))
|
|
79
|
+
core -= Array(dit.must)
|
|
80
|
+
core -= Array(dit.not)
|
|
81
|
+
end
|
|
82
|
+
core -= nott
|
|
83
|
+
core
|
|
84
|
+
else
|
|
85
|
+
Array(@may)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def must(all = true)
|
|
90
|
+
if all
|
|
91
|
+
core = ldap_ancestors.inject([]) do |memo, klass|
|
|
92
|
+
memo |= Array(klass.must(false))
|
|
93
|
+
memo
|
|
94
|
+
end
|
|
95
|
+
if dit = dit_content_rule
|
|
96
|
+
core.push(*Array(dit.must))
|
|
97
|
+
end
|
|
98
|
+
core
|
|
99
|
+
else
|
|
100
|
+
Array(@must)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def aux
|
|
105
|
+
if dit_content_rule
|
|
106
|
+
Array(dit_content_rule.aux)
|
|
107
|
+
else
|
|
108
|
+
[]
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def attributes(all = true)
|
|
113
|
+
may(all) + must(all)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def dit_content_rule
|
|
117
|
+
namespace.dit_content_rule(oid)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def object_class
|
|
121
|
+
@object_class || names.first
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def object_classes
|
|
125
|
+
ldap_ancestors.map {|a| a.object_class}.compact.reverse.uniq
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
alias objectClass object_classes
|
|
129
|
+
|
|
130
|
+
# Converts an attribute name to a human readable form. For compatibility
|
|
131
|
+
# with ActiveRecord.
|
|
132
|
+
#
|
|
133
|
+
# L::User.human_attribute_name(:givenName) #=> "Given name"
|
|
134
|
+
def human_attribute_name(attribute, options={})
|
|
135
|
+
attribute = Ldaptic.encode(attribute)
|
|
136
|
+
if at = namespace.attribute_type(attribute)
|
|
137
|
+
attribute = at.verbose_name
|
|
138
|
+
end
|
|
139
|
+
attribute = attribute[0..0].upcase + attribute[1..-1]
|
|
140
|
+
attribute.gsub!(/([A-Z])([A-Z][a-z])/) { "#$1 #{$2.downcase}" }
|
|
141
|
+
attribute.gsub!(/([a-z\d])([A-Z])/) { "#$1 #{$2.downcase}" }
|
|
142
|
+
attribute.gsub!(/\b[a-z][A-Z]/) { $&.upcase }
|
|
143
|
+
attribute.gsub!('_', '-')
|
|
144
|
+
attribute
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def instantiate(attributes) #:nodoc:
|
|
148
|
+
ocs = attributes["objectClass"].to_a.map {|c| namespace.object_class(c)}
|
|
149
|
+
subclass = (@subclasses.to_a & ocs).detect {|x| !x.auxiliary?}
|
|
150
|
+
if subclass
|
|
151
|
+
return subclass.instantiate(attributes)
|
|
152
|
+
end
|
|
153
|
+
unless structural? || ocs.empty?
|
|
154
|
+
logger.warn "#{name}: invalid object class for #{attributes.inspect}"
|
|
155
|
+
end
|
|
156
|
+
obj = allocate
|
|
157
|
+
obj.instance_variable_set(:@dn, ::Ldaptic::DN(Array(attributes.delete('dn')).first, obj))
|
|
158
|
+
obj.instance_variable_set(:@original_attributes, attributes)
|
|
159
|
+
obj.instance_variable_set(:@attributes, {})
|
|
160
|
+
obj.instance_eval { common_initializations; after_load }
|
|
161
|
+
obj
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
protected
|
|
165
|
+
def inherited(subclass) #:nodoc:
|
|
166
|
+
if superclass != Object
|
|
167
|
+
@subclasses ||= []
|
|
168
|
+
@subclasses << subclass
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def initialize(data = {})
|
|
175
|
+
Ldaptic::Errors.raise(TypeError.new("abstract class initialized")) if self.class.oid.nil? || self.class.abstract?
|
|
176
|
+
@attributes = {}
|
|
177
|
+
data = data.dup
|
|
178
|
+
if dn = data.delete('dn') || data.delete(:dn)
|
|
179
|
+
dn.first if dn.kind_of?(Array)
|
|
180
|
+
self.dn = dn
|
|
181
|
+
end
|
|
182
|
+
merge_attributes(data)
|
|
183
|
+
@attributes['objectClass'] ||= []
|
|
184
|
+
@attributes['objectClass'].insert(0, *self.class.object_classes).uniq!
|
|
185
|
+
common_initializations
|
|
186
|
+
after_build
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def merge_attributes(data)
|
|
190
|
+
# If it's a HashWithIndifferentAccess (eg, params in Rails), convert it
|
|
191
|
+
# to a Hash with symbolic keys. This causes the underscore/hyphen
|
|
192
|
+
# translation to take place in write_attribute. Form helpers in Rails
|
|
193
|
+
# use a method name to read data,
|
|
194
|
+
if defined?(::HashWithIndifferentAccess) && data.is_a?(HashWithIndifferentAccess)
|
|
195
|
+
data = data.symbolize_keys
|
|
196
|
+
end
|
|
197
|
+
data.each do |key, value|
|
|
198
|
+
write_attribute(key, value)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
alias attributes= merge_attributes
|
|
203
|
+
|
|
204
|
+
# A link back to the namespace.
|
|
205
|
+
def namespace
|
|
206
|
+
@namespace || self.class.namespace
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def logger
|
|
210
|
+
self.class.logger
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Returns +self+. For ActiveModel compatibility.
|
|
214
|
+
def to_model
|
|
215
|
+
self
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
attr_reader :dn
|
|
219
|
+
|
|
220
|
+
# The first (relative) component of the distinguished name.
|
|
221
|
+
def rdn
|
|
222
|
+
dn && dn.rdn
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Returns an array containing the DN. For ActiveModel compatibility.
|
|
226
|
+
def to_key
|
|
227
|
+
[dn] if persisted?
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Returns the DN. For ActiveModel compatibility.
|
|
231
|
+
def to_param
|
|
232
|
+
dn if persisted?
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# The parent object containing this one.
|
|
236
|
+
def parent
|
|
237
|
+
unless @parent
|
|
238
|
+
@parent = search(:base => dn.parent, :scope => :base, :limit => true)
|
|
239
|
+
@parent.instance_variable_get(:@children)[rdn] = self
|
|
240
|
+
end
|
|
241
|
+
@parent
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def inspect
|
|
245
|
+
str = "#<#{self.class.inspect} #{dn}"
|
|
246
|
+
(@original_attributes||{}).merge(@attributes).each do |k, values|
|
|
247
|
+
next if values.empty?
|
|
248
|
+
s = (values.size == 1 ? "" : "s")
|
|
249
|
+
at = namespace.attribute_type(k)
|
|
250
|
+
syntax = namespace.attribute_syntax(k)
|
|
251
|
+
if at && syntax && !syntax.x_not_human_readable? && syntax.desc != "Octet String"
|
|
252
|
+
str << " " << k << ": " << values.inspect[1..-2]
|
|
253
|
+
else
|
|
254
|
+
str << " " << k << ": "
|
|
255
|
+
if !at
|
|
256
|
+
str << "(unknown attribute)"
|
|
257
|
+
elsif !syntax
|
|
258
|
+
str << "(unknown type)"
|
|
259
|
+
else
|
|
260
|
+
str << "(" << values.size.to_s << " binary value" << s << ")"
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
str << ">"
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def to_s
|
|
268
|
+
"#<#{self.class} #{dn}>"
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Reads an attribute and typecasts it if neccessary. If the argument given
|
|
272
|
+
# is a symbol, underscores are translated into hyphens. Since
|
|
273
|
+
# #method_missing delegates to this method, method names with underscores
|
|
274
|
+
# map to attributes with hyphens.
|
|
275
|
+
def read_attribute(key)
|
|
276
|
+
key = Ldaptic.encode(key)
|
|
277
|
+
@attributes[key] ||= ((@original_attributes || {}).fetch(key, [])).dup
|
|
278
|
+
Ldaptic::AttributeSet.new(self, key, @attributes[key])
|
|
279
|
+
end
|
|
280
|
+
protected :read_attribute
|
|
281
|
+
|
|
282
|
+
# Returns a hash of attributes.
|
|
283
|
+
def attributes
|
|
284
|
+
(@original_attributes||{}).merge(@attributes).keys.inject({}) do |hash, key|
|
|
285
|
+
hash[key] = read_attribute(key)
|
|
286
|
+
hash
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def changes
|
|
291
|
+
@attributes.reject do |k, v|
|
|
292
|
+
(@original_attributes || {})[k].to_a == v
|
|
293
|
+
end.keys.inject({}) do |hash, key|
|
|
294
|
+
hash[key] = read_attribute(key)
|
|
295
|
+
hash
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# Change an attribute. This is called by #method_missing and
|
|
300
|
+
# <tt>[]=</tt>.
|
|
301
|
+
#
|
|
302
|
+
# Changes are not committed to the server until #save is called.
|
|
303
|
+
def write_attribute(key, values)
|
|
304
|
+
set = read_attribute(key)
|
|
305
|
+
if values.respond_to?(:to_str) && set.syntax_object && set.syntax_object.error("1\n1")
|
|
306
|
+
values = values.split(/\r?\n/)
|
|
307
|
+
elsif values == ''
|
|
308
|
+
values = []
|
|
309
|
+
end
|
|
310
|
+
set.replace(values)
|
|
311
|
+
end
|
|
312
|
+
protected :write_attribute
|
|
313
|
+
|
|
314
|
+
# Note the values are not typecast and thus must be strings.
|
|
315
|
+
def modify_attribute(action, key, *values)
|
|
316
|
+
key = Ldaptic.encode(key)
|
|
317
|
+
values.flatten!.map! {|v| Ldaptic.encode(v)}
|
|
318
|
+
@original_attributes[key] ||= []
|
|
319
|
+
virgin = @original_attributes[key].dup
|
|
320
|
+
original = Ldaptic::AttributeSet.new(self, key, @original_attributes[key])
|
|
321
|
+
original.__send__(action, values)
|
|
322
|
+
begin
|
|
323
|
+
namespace.modify(dn, [[action, key, values]])
|
|
324
|
+
rescue
|
|
325
|
+
@original_attributes[key] = virgin
|
|
326
|
+
raise $!
|
|
327
|
+
end
|
|
328
|
+
if @attributes[key]
|
|
329
|
+
read_attribute(key).__send__(action, values)
|
|
330
|
+
end
|
|
331
|
+
self
|
|
332
|
+
end
|
|
333
|
+
private :modify_attribute
|
|
334
|
+
|
|
335
|
+
# Commit an array of modifications directly to LDAP, without updating the
|
|
336
|
+
# local object.
|
|
337
|
+
def modify_attributes(mods) #:nodoc:
|
|
338
|
+
namespace.modify(dn, mods)
|
|
339
|
+
self
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def add!(key, *values) #:nodoc:
|
|
343
|
+
modify_attribute(:add, key, values)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def replace!(key, *values) #:nodoc:
|
|
347
|
+
modify_attribute(:replace, key, values)
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def delete!(key, *values) #:nodoc:
|
|
351
|
+
modify_attribute(:delete, key, values)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
# Compare an attribute to see if it has a given value. This happens at the
|
|
355
|
+
# server.
|
|
356
|
+
def compare(key, value)
|
|
357
|
+
namespace.compare(dn, key, value)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
# attr_reader :attributes
|
|
361
|
+
def attribute_names
|
|
362
|
+
attributes.keys
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def ldap_ancestors
|
|
366
|
+
self.class.ldap_ancestors | objectClass.map {|c|namespace.object_class(c)}
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def aux
|
|
370
|
+
self['objectClass'].map {|c| namespace.object_class(c)} - self.class.ldap_ancestors
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def must(all = true)
|
|
374
|
+
self.class.must(all) + aux.map {|a|a.must(false)}.flatten
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def may(all = true)
|
|
378
|
+
self.class.may(all) + aux.map {|a|a.may(false)}.flatten
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def may_must(attribute)
|
|
382
|
+
attribute = Ldaptic.encode(attribute)
|
|
383
|
+
if must.include?(attribute)
|
|
384
|
+
:must
|
|
385
|
+
elsif may.include?(attribute)
|
|
386
|
+
:may
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def respond_to?(method, *) #:nodoc:
|
|
391
|
+
both = may + must
|
|
392
|
+
super || (both + both.map {|x| "#{x}="} + both.map {|x| "#{x}-before-type-cast"}).include?(Ldaptic.encode(method.to_sym))
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
# Delegates to +read_attribute+ or +write_attribute+. Pops an element out
|
|
396
|
+
# of its set if the attribute is marked SINGLE-VALUE.
|
|
397
|
+
def method_missing(method, *args, &block)
|
|
398
|
+
attribute = Ldaptic.encode(method)
|
|
399
|
+
if attribute[-1] == ?=
|
|
400
|
+
attribute.chop!
|
|
401
|
+
if may_must(attribute)
|
|
402
|
+
return write_attribute(attribute, *args, &block)
|
|
403
|
+
end
|
|
404
|
+
elsif attribute[-1] == ??
|
|
405
|
+
attribute.chop!
|
|
406
|
+
if may_must(attribute)
|
|
407
|
+
if args.empty?
|
|
408
|
+
return !read_attribute(attribute).empty?
|
|
409
|
+
else
|
|
410
|
+
return args.flatten.any? {|arg| compare(attribute, arg)}
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
elsif attribute =~ /\A(.*)-before-type-cast\z/ && may_must($1)
|
|
414
|
+
return read_attribute($1, *args, &block)
|
|
415
|
+
elsif may_must(attribute)
|
|
416
|
+
return read_attribute(attribute, *args, &block).one
|
|
417
|
+
end
|
|
418
|
+
super(method, *args, &block)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
# Searches for children. This is identical to Ldaptic::Base#search, only
|
|
422
|
+
# the default base is the current object's DN.
|
|
423
|
+
def search(options, &block)
|
|
424
|
+
if options[:base].kind_of?(Hash)
|
|
425
|
+
options = options.merge(:base => dn/options[:base])
|
|
426
|
+
end
|
|
427
|
+
namespace.search({:base => dn}.merge(options), &block)
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
# Searches for a child, given an RDN.
|
|
431
|
+
def /(*args)
|
|
432
|
+
search(:base => dn.send(:/, *args), :scope => :base, :limit => true)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
alias find /
|
|
436
|
+
|
|
437
|
+
def fetch(dn = self.dn, options = {}) #:nodoc:
|
|
438
|
+
search({:base => dn}.merge(options))
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
# If a Hash or a String containing "=" is given, the argument is treated as
|
|
442
|
+
# an RDN and a search for a child is performed. +nil+ is returned if no
|
|
443
|
+
# match is found.
|
|
444
|
+
#
|
|
445
|
+
# For a singular String or Symbol argument, that attribute is read with
|
|
446
|
+
# read_attribute. Unlike with method_missing, an array is always returned,
|
|
447
|
+
# making this variant useful for metaprogramming.
|
|
448
|
+
def [](key)
|
|
449
|
+
if key.kind_of?(Hash) || key =~ /=/
|
|
450
|
+
cached_child(key)
|
|
451
|
+
else
|
|
452
|
+
read_attribute(key)
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
def []=(key, value)
|
|
457
|
+
if key.kind_of?(Hash) || key =~ /=/
|
|
458
|
+
assign_child(key, value)
|
|
459
|
+
else
|
|
460
|
+
write_attribute(key, value)
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
# Has the object been saved before?
|
|
465
|
+
def persisted?
|
|
466
|
+
!!@original_attributes
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def errors
|
|
470
|
+
@errors ||= Ldaptic::ErrorSet.new(self)
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
def valid?
|
|
474
|
+
errors.clear
|
|
475
|
+
check_server_constraints
|
|
476
|
+
errors.empty?
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Inverse of #valid?
|
|
480
|
+
def invalid?(*args)
|
|
481
|
+
!valid?(*args)
|
|
482
|
+
end
|
|
483
|
+
|
|
484
|
+
def check_server_constraints
|
|
485
|
+
if changes.has_key?('objectClass')
|
|
486
|
+
(attributes.keys - may - must) | must | changes.keys
|
|
487
|
+
else
|
|
488
|
+
changes.keys
|
|
489
|
+
end.each do |k|
|
|
490
|
+
set = read_attribute(k)
|
|
491
|
+
set.errors.each do |message|
|
|
492
|
+
errors.add(k, message)
|
|
493
|
+
end
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
private :check_server_constraints
|
|
497
|
+
|
|
498
|
+
# For new objects, does an LDAP add. For existing objects, does an LDAP
|
|
499
|
+
# modify. This only sends the modified attributes to the server. If a
|
|
500
|
+
# server constraint was violated, populates #errors and returns false.
|
|
501
|
+
def save
|
|
502
|
+
return false unless valid?
|
|
503
|
+
if persisted?
|
|
504
|
+
namespace.modify(dn, changes)
|
|
505
|
+
else
|
|
506
|
+
namespace.add(dn, changes)
|
|
507
|
+
end
|
|
508
|
+
@original_attributes = (@original_attributes||{}).merge(@attributes)
|
|
509
|
+
@attributes = {}
|
|
510
|
+
true
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
# Like #save, but raise an exception if the entry could not be saved.
|
|
514
|
+
def save!
|
|
515
|
+
save ? self : raise(EntryNotSaved)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# Assign the given attribute hash, then #save.
|
|
519
|
+
def update_attributes(hash)
|
|
520
|
+
merge_attributes(hash)
|
|
521
|
+
save
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Like #update_attributes but raise on failure.
|
|
525
|
+
def update_attributes!(hash)
|
|
526
|
+
merge_attributes(hash)
|
|
527
|
+
save!
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# Refetches the attributes from the server.
|
|
531
|
+
def reload
|
|
532
|
+
new = search(:scope => :base, :limit => true)
|
|
533
|
+
@original_attributes = new.instance_variable_get(:@original_attributes)
|
|
534
|
+
@attributes = new.instance_variable_get(:@attributes)
|
|
535
|
+
@dn = Ldaptic::DN(new.dn, self)
|
|
536
|
+
@children = {}
|
|
537
|
+
self
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
# Deletes the object from the server. If #save is invoked afterwards, the
|
|
541
|
+
# entry will be recreated.
|
|
542
|
+
def delete
|
|
543
|
+
namespace.delete(dn)
|
|
544
|
+
@attributes = (@original_attributes||{}).merge(@attributes)
|
|
545
|
+
@original_attributes = nil
|
|
546
|
+
self
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Alias for #delete.
|
|
550
|
+
def destroy
|
|
551
|
+
delete
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
def rename(new_rdn, delete_old = nil)
|
|
555
|
+
old_rdn = rdn
|
|
556
|
+
if new_rdn.kind_of?(Ldaptic::DN)
|
|
557
|
+
new_root = new_rdn.parent
|
|
558
|
+
new_rdn = new_rdn.rdn
|
|
559
|
+
else
|
|
560
|
+
new_rdn = Ldaptic::RDN(new_rdn)
|
|
561
|
+
new_root = nil
|
|
562
|
+
end
|
|
563
|
+
if delete_old.nil?
|
|
564
|
+
delete_old = (new_rdn == old_rdn)
|
|
565
|
+
end
|
|
566
|
+
namespace.rename(dn, new_rdn.to_str, delete_old, *[new_root].compact)
|
|
567
|
+
if delete_old
|
|
568
|
+
old_rdn.each do |k, v|
|
|
569
|
+
[@attributes, @original_attributes].each do |hash|
|
|
570
|
+
hash.delete_if {|k2, v2| k.to_s.downcase == k2.to_s.downcase && v.to_s.downcase == v2.to_s.downcase }
|
|
571
|
+
end
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
old_dn = Ldaptic::DN(@dn, self)
|
|
575
|
+
@dn = nil
|
|
576
|
+
if new_root
|
|
577
|
+
self.dn = new_root / new_rdn
|
|
578
|
+
else
|
|
579
|
+
self.dn = old_dn.parent / new_rdn
|
|
580
|
+
end
|
|
581
|
+
write_attributes_from_rdn(rdn, @original_attributes)
|
|
582
|
+
if @parent
|
|
583
|
+
children = @parent.instance_variable_get(:@children)
|
|
584
|
+
if child = children.delete(old_rdn)
|
|
585
|
+
children[new_rdn] = child if child == self
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
self
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
protected
|
|
592
|
+
|
|
593
|
+
def dn=(value)
|
|
594
|
+
if @dn
|
|
595
|
+
Ldaptic::Errors.raise(Ldaptic::Error.new("can't reassign DN"))
|
|
596
|
+
end
|
|
597
|
+
@dn = ::Ldaptic::DN(value, self)
|
|
598
|
+
write_attributes_from_rdn(rdn)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
private
|
|
602
|
+
|
|
603
|
+
def after_build
|
|
604
|
+
end
|
|
605
|
+
def after_load
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def common_initializations
|
|
609
|
+
@children ||= {}
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def write_attributes_from_rdn(rdn, attributes = @attributes)
|
|
613
|
+
Ldaptic::RDN(rdn).each do |k, v|
|
|
614
|
+
attributes[k.to_s.downcase] ||= []
|
|
615
|
+
attributes[k.to_s.downcase] |= [v]
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
def cached_child(rdn = nil)
|
|
620
|
+
return self if rdn.nil? || rdn.empty?
|
|
621
|
+
rdn = Ldaptic::RDN(rdn)
|
|
622
|
+
return @children[rdn] if @children.has_key?(rdn)
|
|
623
|
+
child = search(:base => rdn, :scope => :base, :limit => true)
|
|
624
|
+
child.instance_variable_set(:@parent, self)
|
|
625
|
+
@children[rdn] = child
|
|
626
|
+
rescue Ldaptic::Errors::NoSuchObject
|
|
627
|
+
end
|
|
628
|
+
|
|
629
|
+
def assign_child(rdn, child)
|
|
630
|
+
unless child.respond_to?(:dn)
|
|
631
|
+
Ldaptic::Errors.raise(TypeError.new("#{child.class} cannot be a child"))
|
|
632
|
+
end
|
|
633
|
+
if child.dn
|
|
634
|
+
Ldaptic::Errors.raise(Ldaptic::Error.new("#{child.class} already has a DN of #{child.dn}"))
|
|
635
|
+
end
|
|
636
|
+
rdn = Ldaptic::RDN(rdn)
|
|
637
|
+
if cached_child(rdn)
|
|
638
|
+
Ldaptic::Errors.raise(Ldaptic::Error.new("child #{[rdn, dn].join(",")} already exists"))
|
|
639
|
+
end
|
|
640
|
+
@children[rdn] = child
|
|
641
|
+
child.dn = Ldaptic::DN(dn/rdn, child)
|
|
642
|
+
child.instance_variable_set(:@parent, self)
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
end
|
|
646
|
+
end
|