ruby-activeldap 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +5 -0
- data/Manifest.txt +91 -25
- data/README +22 -0
- data/Rakefile +41 -8
- data/TODO +1 -6
- data/examples/config.yaml.example +5 -0
- data/examples/example.der +0 -0
- data/examples/example.jpg +0 -0
- data/examples/groupadd +41 -0
- data/examples/groupdel +35 -0
- data/examples/groupls +49 -0
- data/examples/groupmod +42 -0
- data/examples/lpasswd +55 -0
- data/examples/objects/group.rb +13 -0
- data/examples/objects/ou.rb +4 -0
- data/examples/objects/user.rb +20 -0
- data/examples/ouadd +38 -0
- data/examples/useradd +45 -0
- data/examples/useradd-binary +50 -0
- data/examples/userdel +34 -0
- data/examples/userls +50 -0
- data/examples/usermod +42 -0
- data/examples/usermod-binary-add +47 -0
- data/examples/usermod-binary-add-time +51 -0
- data/examples/usermod-binary-del +48 -0
- data/examples/usermod-lang-add +43 -0
- data/lib/active_ldap.rb +213 -214
- data/lib/active_ldap/adapter/base.rb +461 -0
- data/lib/active_ldap/adapter/ldap.rb +232 -0
- data/lib/active_ldap/adapter/ldap_ext.rb +69 -0
- data/lib/active_ldap/adapter/net_ldap.rb +288 -0
- data/lib/active_ldap/adapter/net_ldap_ext.rb +29 -0
- data/lib/active_ldap/association/belongs_to.rb +3 -1
- data/lib/active_ldap/association/belongs_to_many.rb +5 -6
- data/lib/active_ldap/association/has_many.rb +9 -17
- data/lib/active_ldap/association/has_many_wrap.rb +4 -5
- data/lib/active_ldap/attributes.rb +4 -0
- data/lib/active_ldap/base.rb +201 -56
- data/lib/active_ldap/configuration.rb +11 -1
- data/lib/active_ldap/connection.rb +15 -9
- data/lib/active_ldap/distinguished_name.rb +246 -0
- data/lib/active_ldap/ldap_error.rb +74 -0
- data/lib/active_ldap/object_class.rb +9 -5
- data/lib/active_ldap/schema.rb +50 -9
- data/lib/active_ldap/validations.rb +11 -13
- data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +7 -0
- data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +21 -0
- data/rails/plugin/active_ldap/init.rb +10 -4
- data/test/al-test-utils.rb +46 -3
- data/test/run-test.rb +16 -4
- data/test/test-unit-ext/always-show-result.rb +28 -0
- data/test/test-unit-ext/priority.rb +163 -0
- data/test/test_adapter.rb +81 -0
- data/test/test_attributes.rb +8 -1
- data/test/test_base.rb +132 -3
- data/test/test_base_per_instance.rb +14 -3
- data/test/test_connection.rb +19 -0
- data/test/test_dn.rb +161 -0
- data/test/test_find.rb +24 -0
- data/test/test_object_class.rb +15 -2
- data/test/test_schema.rb +108 -1
- metadata +111 -41
- data/lib/active_ldap/adaptor/base.rb +0 -29
- data/lib/active_ldap/adaptor/ldap.rb +0 -466
- data/lib/active_ldap/ldap.rb +0 -113
@@ -0,0 +1,29 @@
|
|
1
|
+
require_library_or_gem 'net/ldap'
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class LDAP
|
5
|
+
class Entry
|
6
|
+
alias initialize_without_original_attribute_names initialize
|
7
|
+
def initialize(*args)
|
8
|
+
@original_attribute_names = []
|
9
|
+
initialize_without_original_attribute_names(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
alias aset_without_original_attribute_names []=
|
13
|
+
def []=(name, value)
|
14
|
+
@original_attribute_names << name
|
15
|
+
aset_without_original_attribute_names(name, value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def original_attribute_names
|
19
|
+
@original_attribute_names.compact.uniq
|
20
|
+
end
|
21
|
+
|
22
|
+
def each_attribute
|
23
|
+
attribute_names.sort_by {|name| name.to_s}.each do |name|
|
24
|
+
yield name, self[name]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -28,7 +28,9 @@ module ActiveLdap
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def find_target
|
31
|
-
|
31
|
+
value = @owner[@options[:foreign_key_name]]
|
32
|
+
raise EntryNotFound if value.nil?
|
33
|
+
filter = {primary_key => value}
|
32
34
|
result = foreign_class.find(:all, :filter => filter, :limit => 1)
|
33
35
|
raise EntryNotFound if result.empty?
|
34
36
|
result.first
|
@@ -28,12 +28,11 @@ module ActiveLdap
|
|
28
28
|
|
29
29
|
def find_target
|
30
30
|
key = @options[:many]
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
foreign_class.find(:all, :filter => "(|#{filter})")
|
31
|
+
values = @owner[@options[:foreign_key_name], true].compact
|
32
|
+
components = values.collect do |value|
|
33
|
+
[key, value]
|
34
|
+
end
|
35
|
+
foreign_class.find(:all, :filter => [:or, *components])
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
@@ -11,7 +11,7 @@ module ActiveLdap
|
|
11
11
|
|
12
12
|
def find_target
|
13
13
|
foreign_base_key = primary_key
|
14
|
-
|
14
|
+
components = @owner[@options[:foreign_key_name], true].collect do |value|
|
15
15
|
key = val = nil
|
16
16
|
if foreign_base_key == "dn"
|
17
17
|
key, val = value.split(",")[0].split("=") unless value.empty?
|
@@ -21,27 +21,19 @@ module ActiveLdap
|
|
21
21
|
[key, val]
|
22
22
|
end.reject do |key, val|
|
23
23
|
key.nil? or val.nil?
|
24
|
-
end
|
25
|
-
|
26
|
-
end.join
|
27
|
-
foreign_class.find(:all, :filter => "(|#{filter})")
|
24
|
+
end
|
25
|
+
foreign_class.find(:all, :filter => [:or, *components])
|
28
26
|
end
|
29
27
|
|
30
28
|
def delete_entries(entries)
|
31
29
|
key = primary_key
|
32
|
-
|
33
|
-
filter = @owner[@options[:foreign_key_name], true].reject do |value|
|
30
|
+
components = @owner[@options[:foreign_key_name], true].reject do |value|
|
34
31
|
value.nil?
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
"(#{dn_attribute}=#{entry.id})"
|
41
|
-
end.join
|
42
|
-
entry_filter = "(|#{entry_filter})"
|
43
|
-
foreign_class.update_all({primary_key => []},
|
44
|
-
"(&#{filter}#{entry_filter})")
|
32
|
+
end
|
33
|
+
filter = [:and,
|
34
|
+
[:and, {key => components}],
|
35
|
+
[:or, {foreign_class.dn_attribute => entries.collect(&:id)}]]
|
36
|
+
foreign_class.update_all({key => []}, filter)
|
45
37
|
end
|
46
38
|
end
|
47
39
|
end
|
@@ -27,7 +27,7 @@ module ActiveLdap
|
|
27
27
|
foreign_base_key = primary_key
|
28
28
|
requested_targets = @owner[@options[:wrap], true]
|
29
29
|
|
30
|
-
|
30
|
+
components = requested_targets.collect do |value|
|
31
31
|
key = val = nil
|
32
32
|
if foreign_base_key == "dn"
|
33
33
|
key, val = value.split(",")[0].split("=") unless value.empty?
|
@@ -37,13 +37,12 @@ module ActiveLdap
|
|
37
37
|
[key, val]
|
38
38
|
end.reject do |key, val|
|
39
39
|
key.nil? or val.nil?
|
40
|
-
end
|
41
|
-
|
42
|
-
end.join
|
40
|
+
end
|
41
|
+
return [] if components.empty?
|
43
42
|
|
44
43
|
klass = foreign_class
|
45
44
|
found_targets = {}
|
46
|
-
klass.find(:all, :filter =>
|
45
|
+
klass.find(:all, :filter => [:or, *components]).each do |target|
|
47
46
|
found_targets[target.send(foreign_base_key)] ||= target
|
48
47
|
end
|
49
48
|
|
data/lib/active_ldap/base.rb
CHANGED
@@ -104,10 +104,13 @@ module ActiveLdap
|
|
104
104
|
end
|
105
105
|
|
106
106
|
class DistinguishedNameInvalid < Error
|
107
|
-
attr_reader :dn
|
108
|
-
def initialize(dn)
|
107
|
+
attr_reader :dn, :reason
|
108
|
+
def initialize(dn, reason=nil)
|
109
109
|
@dn = dn
|
110
|
-
|
110
|
+
@reason = reason
|
111
|
+
message = "#{@dn} is invalid distinguished name (dn)"
|
112
|
+
message << ": #{@reason}"if @reason
|
113
|
+
super(message)
|
111
114
|
end
|
112
115
|
end
|
113
116
|
|
@@ -126,7 +129,7 @@ module ActiveLdap
|
|
126
129
|
class EntryInvalid < Error
|
127
130
|
end
|
128
131
|
|
129
|
-
class
|
132
|
+
class OperationNotPermitted < Error
|
130
133
|
end
|
131
134
|
|
132
135
|
class ConnectionNotEstablished < Error
|
@@ -135,6 +138,14 @@ module ActiveLdap
|
|
135
138
|
class AdapterNotSpecified < Error
|
136
139
|
end
|
137
140
|
|
141
|
+
class AdapterNotFound < Error
|
142
|
+
attr_reader :adapter
|
143
|
+
def initialize(adapter)
|
144
|
+
@adapter = adapter
|
145
|
+
super("LDAP configuration specifies nonexistent #{@adapter} adapter")
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
138
149
|
class UnknownAttribute < Error
|
139
150
|
attr_reader :name
|
140
151
|
def initialize(name)
|
@@ -155,7 +166,11 @@ module ActiveLdap
|
|
155
166
|
include Reloadable::Subclasses
|
156
167
|
end
|
157
168
|
|
158
|
-
VALID_LDAP_MAPPING_OPTIONS = [:dn_attribute, :prefix, :
|
169
|
+
VALID_LDAP_MAPPING_OPTIONS = [:dn_attribute, :prefix, :scope,
|
170
|
+
:classes, :recommended_classes]
|
171
|
+
VALID_SEARCH_OPTIONS = [:attribute, :value, :filter, :prefix,
|
172
|
+
:classes, :scope, :limit, :attributes,
|
173
|
+
:sort_by, :order]
|
159
174
|
|
160
175
|
cattr_accessor :logger
|
161
176
|
cattr_accessor :configurations
|
@@ -189,12 +204,12 @@ module ActiveLdap
|
|
189
204
|
end
|
190
205
|
|
191
206
|
class_local_attr_accessor false, :prefix, :base, :dn_attribute
|
192
|
-
class_local_attr_accessor true, :ldap_scope
|
207
|
+
class_local_attr_accessor true, :ldap_scope
|
208
|
+
class_local_attr_accessor true, :required_classes, :recommended_classes
|
193
209
|
|
194
210
|
class << self
|
195
211
|
# Hide new in Base
|
196
212
|
private :new
|
197
|
-
private :dn_attribute
|
198
213
|
|
199
214
|
# Connect and bind to LDAP creating a class variable for use by
|
200
215
|
# all ActiveLdap objects.
|
@@ -213,6 +228,8 @@ module ActiveLdap
|
|
213
228
|
# :base overwrites Base.base - this affects EVERYTHING
|
214
229
|
# :try_sasl indicates that a SASL bind should be attempted when binding
|
215
230
|
# to the server (default: false)
|
231
|
+
# :sasl_mechanisms is an array of SASL mechanism to try
|
232
|
+
# (default: ["GSSAPI", "CRAM-MD5", "EXTERNAL"])
|
216
233
|
# :allow_anonymous indicates that a true anonymous bind is allowed when
|
217
234
|
# trying to bind to the server (default: true)
|
218
235
|
# :retries - indicates the number of attempts to reconnect that will be
|
@@ -246,10 +263,12 @@ module ActiveLdap
|
|
246
263
|
end
|
247
264
|
|
248
265
|
def search(options={}, &block)
|
266
|
+
validate_search_options(options)
|
249
267
|
attr = options[:attribute]
|
250
268
|
value = options[:value] || '*'
|
251
269
|
filter = options[:filter]
|
252
270
|
prefix = options[:prefix]
|
271
|
+
classes = options[:classes]
|
253
272
|
|
254
273
|
value = value.first if value.is_a?(Array) and value.first.size == 1
|
255
274
|
if filter.nil? and !value.is_a?(String)
|
@@ -259,13 +278,21 @@ module ActiveLdap
|
|
259
278
|
_attr, value, _prefix = split_search_value(value)
|
260
279
|
attr ||= _attr || dn_attribute || "objectClass"
|
261
280
|
prefix ||= _prefix
|
262
|
-
filter
|
281
|
+
if filter.nil?
|
282
|
+
filter = "(#{attr}=#{escape_filter_value(value, true)})"
|
283
|
+
filter = "(&#{filter}#{object_class_filters(classes)})"
|
284
|
+
end
|
263
285
|
_base = [prefix, base].compact.reject{|x| x.empty?}.join(",")
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
286
|
+
search_options = {
|
287
|
+
:base => _base,
|
288
|
+
:scope => options[:scope] || ldap_scope,
|
289
|
+
:filter => filter,
|
290
|
+
:limit => options[:limit],
|
291
|
+
:attributes => options[:attributes],
|
292
|
+
:sort_by => options[:sort_by],
|
293
|
+
:order => options[:order],
|
294
|
+
}
|
295
|
+
connection.search(search_options) do |dn, attrs|
|
269
296
|
attributes = {}
|
270
297
|
attrs.each do |key, value|
|
271
298
|
normalized_attr, normalized_value = make_subtypes(key, value)
|
@@ -290,15 +317,16 @@ module ActiveLdap
|
|
290
317
|
dn_attribute = options[:dn_attribute] || default_dn_attribute
|
291
318
|
prefix = options[:prefix] || default_prefix
|
292
319
|
classes = options[:classes]
|
320
|
+
recommended_classes = options[:recommended_classes]
|
293
321
|
scope = options[:scope]
|
294
322
|
|
295
323
|
self.dn_attribute = dn_attribute
|
296
324
|
self.prefix = prefix
|
297
325
|
self.ldap_scope = scope
|
298
326
|
self.required_classes = classes
|
327
|
+
self.recommended_classes = recommended_classes
|
299
328
|
|
300
329
|
public_class_method :new
|
301
|
-
public_class_method :dn_attribute
|
302
330
|
end
|
303
331
|
|
304
332
|
alias_method :base_inheritable, :base
|
@@ -406,7 +434,7 @@ module ActiveLdap
|
|
406
434
|
|
407
435
|
# find
|
408
436
|
#
|
409
|
-
# Finds the first match for value where |value| is the value of some
|
437
|
+
# Finds the first match for value where |value| is the value of some
|
410
438
|
# |field|, or the wildcard match. This is only useful for derived classes.
|
411
439
|
# usage: Subclass.find(:attribute => "cn", :value => "some*val")
|
412
440
|
# Subclass.find('some*val')
|
@@ -424,10 +452,20 @@ module ActiveLdap
|
|
424
452
|
end
|
425
453
|
|
426
454
|
def exists?(dn, options={})
|
427
|
-
prefix = /^#{Regexp.escape(truncate_base(ensure_dn_attribute(dn)))}/
|
428
|
-
|
455
|
+
prefix = /^#{Regexp.escape(truncate_base(ensure_dn_attribute(dn)))}/ #
|
456
|
+
dn_suffix = nil
|
429
457
|
not search({:value => dn}.merge(options)).find do |_dn,|
|
430
|
-
prefix.match(_dn)
|
458
|
+
if prefix.match(_dn)
|
459
|
+
begin
|
460
|
+
dn_suffix ||= DN.parse(base)
|
461
|
+
dn_prefix = DN.parse(_dn) - dn_suffix
|
462
|
+
true
|
463
|
+
rescue DistinguishedNameInvalid, ArgumentError
|
464
|
+
false
|
465
|
+
end
|
466
|
+
else
|
467
|
+
false
|
468
|
+
end
|
431
469
|
end.nil?
|
432
470
|
end
|
433
471
|
|
@@ -449,10 +487,10 @@ module ActiveLdap
|
|
449
487
|
def update_all(attributes, filter=nil, options={})
|
450
488
|
search_options = options
|
451
489
|
if filter
|
452
|
-
if /[=\(\)&\|]/
|
453
|
-
search_options = search_options.merge(:filter => filter)
|
454
|
-
else
|
490
|
+
if filter.is_a?(String) and /[=\(\)&\|]/ !~ filter
|
455
491
|
search_options = search_options.merge(:value => filter)
|
492
|
+
else
|
493
|
+
search_options = search_options.merge(:filter => filter)
|
456
494
|
end
|
457
495
|
end
|
458
496
|
targets = search(search_options).collect do |dn, attrs|
|
@@ -486,18 +524,56 @@ module ActiveLdap
|
|
486
524
|
options.assert_valid_keys(VALID_LDAP_MAPPING_OPTIONS)
|
487
525
|
end
|
488
526
|
|
527
|
+
def validate_search_options(options)
|
528
|
+
options.assert_valid_keys(VALID_SEARCH_OPTIONS)
|
529
|
+
end
|
530
|
+
|
489
531
|
def extract_options_from_args!(args)
|
490
532
|
args.last.is_a?(Hash) ? args.pop : {}
|
491
533
|
end
|
492
534
|
|
535
|
+
def object_class_filters(classes=nil)
|
536
|
+
(classes || required_classes).collect do |name|
|
537
|
+
"(objectClass=#{escape_filter_value(name, true)})"
|
538
|
+
end.join("")
|
539
|
+
end
|
540
|
+
|
493
541
|
def find_initial(options)
|
494
542
|
find_every(options.merge(:limit => 1)).first
|
495
543
|
end
|
496
544
|
|
545
|
+
def normalize_sort_order(value)
|
546
|
+
case value.to_s
|
547
|
+
when /\Aasc(?:end)?\z/i
|
548
|
+
:ascend
|
549
|
+
when /\Adesc(?:end)?\z/i
|
550
|
+
:descend
|
551
|
+
else
|
552
|
+
raise ArgumentError, "Invalid order: #{value.inspect}"
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
497
556
|
def find_every(options)
|
498
|
-
|
557
|
+
options = options.dup
|
558
|
+
sort_by = options.delete(:sort_by)
|
559
|
+
order = options.delete(:order)
|
560
|
+
limit = options.delete(:limit) if sort_by or order
|
561
|
+
|
562
|
+
results = search(options).collect do |dn, attrs|
|
499
563
|
instantiate([dn, attrs])
|
500
564
|
end
|
565
|
+
return results if sort_by.nil? and order.nil?
|
566
|
+
|
567
|
+
sort_by ||= "dn"
|
568
|
+
if sort_by.downcase == "dn"
|
569
|
+
results = results.sort_by {|result| DN.parse(result.dn)}
|
570
|
+
else
|
571
|
+
results = results.sort_by {|result| result.send(sort_by)}
|
572
|
+
end
|
573
|
+
|
574
|
+
results.reverse! if normalize_sort_order(order || "ascend") == :descend
|
575
|
+
results = results[0, limit] if limit
|
576
|
+
results
|
501
577
|
end
|
502
578
|
|
503
579
|
def find_from_dns(dns, options)
|
@@ -519,8 +595,12 @@ module ActiveLdap
|
|
519
595
|
|
520
596
|
def find_one(dn, options)
|
521
597
|
attr, value, prefix = split_search_value(dn)
|
522
|
-
|
523
|
-
|
598
|
+
filters = [
|
599
|
+
"(#{attr || dn_attribute}=#{escape_filter_value(value, true)})",
|
600
|
+
object_class_filters(options[:classes]),
|
601
|
+
options[:filter],
|
602
|
+
]
|
603
|
+
filter = "(&#{filters.compact.join('')})"
|
524
604
|
options = {:prefix => prefix}.merge(options.merge(:filter => filter))
|
525
605
|
result = find_initial(options)
|
526
606
|
if result
|
@@ -542,8 +622,12 @@ module ActiveLdap
|
|
542
622
|
end
|
543
623
|
filter
|
544
624
|
end
|
545
|
-
|
546
|
-
|
625
|
+
filters = [
|
626
|
+
"(|#{dn_filters.join('')})",
|
627
|
+
object_class_filters(options[:classes]),
|
628
|
+
options[:filter],
|
629
|
+
]
|
630
|
+
filter = "(&#{filters.compact.join('')})"
|
547
631
|
result = find_every(options.merge(:filter => filter))
|
548
632
|
if result.size == dns.size
|
549
633
|
result
|
@@ -555,9 +639,22 @@ module ActiveLdap
|
|
555
639
|
end
|
556
640
|
|
557
641
|
def split_search_value(value)
|
558
|
-
|
559
|
-
|
560
|
-
|
642
|
+
attr = prefix = nil
|
643
|
+
begin
|
644
|
+
dn = DN.parse(value)
|
645
|
+
attr, value = dn.rdns.first.to_a.first
|
646
|
+
rest = dn.rdns[1..-1]
|
647
|
+
prefix = DN.new(*rest).to_s unless rest.empty?
|
648
|
+
rescue DistinguishedNameInvalid
|
649
|
+
begin
|
650
|
+
dn = DN.parse("DUMMY=#{value}")
|
651
|
+
_, value = dn.rdns.first.to_a.first
|
652
|
+
rest = dn.rdns[1..-1]
|
653
|
+
prefix = DN.new(*rest).to_s unless rest.empty?
|
654
|
+
rescue DistinguishedNameInvalid
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
561
658
|
prefix = nil if prefix == base
|
562
659
|
prefix = truncate_base(prefix) if prefix
|
563
660
|
[attr, value, prefix]
|
@@ -580,7 +677,7 @@ module ActiveLdap
|
|
580
677
|
|
581
678
|
def ensure_dn_attribute(target)
|
582
679
|
"#{dn_attribute}=" +
|
583
|
-
target.gsub(
|
680
|
+
target.gsub(/^\s*#{Regexp.escape(dn_attribute)}\s*=\s*/i, '')
|
584
681
|
end
|
585
682
|
|
586
683
|
def ensure_base(target)
|
@@ -588,7 +685,15 @@ module ActiveLdap
|
|
588
685
|
end
|
589
686
|
|
590
687
|
def truncate_base(target)
|
591
|
-
target
|
688
|
+
if /,/ =~ target
|
689
|
+
begin
|
690
|
+
(DN.parse(target) - DN.parse(base)).to_s
|
691
|
+
rescue DistinguishedNameInvalid, ArgumentError
|
692
|
+
target
|
693
|
+
end
|
694
|
+
else
|
695
|
+
target
|
696
|
+
end
|
592
697
|
end
|
593
698
|
|
594
699
|
def ensure_logger
|
@@ -623,7 +728,12 @@ module ActiveLdap
|
|
623
728
|
|
624
729
|
def default_dn_attribute
|
625
730
|
if name.empty?
|
626
|
-
|
731
|
+
dn_attribute = nil
|
732
|
+
parent_class = ancestors[1]
|
733
|
+
if parent_class.respond_to?(:dn_attribute)
|
734
|
+
dn_attribute = parent_class.dn_attribute
|
735
|
+
end
|
736
|
+
dn_attribute || "cn"
|
627
737
|
else
|
628
738
|
Inflector.underscore(Inflector.demodulize(name))
|
629
739
|
end
|
@@ -640,6 +750,7 @@ module ActiveLdap
|
|
640
750
|
|
641
751
|
self.ldap_scope = :sub
|
642
752
|
self.required_classes = ['top']
|
753
|
+
self.recommended_classes = []
|
643
754
|
|
644
755
|
include Enumerable
|
645
756
|
|
@@ -653,12 +764,15 @@ module ActiveLdap
|
|
653
764
|
def initialize(attributes=nil)
|
654
765
|
init_base
|
655
766
|
@new_entry = true
|
656
|
-
|
657
|
-
|
767
|
+
initial_classes = required_classes | recommended_classes
|
768
|
+
if attributes.nil?
|
769
|
+
apply_object_class(initial_classes)
|
770
|
+
elsif attributes.is_a?(String) or attributes.is_a?(Array)
|
771
|
+
apply_object_class(initial_classes)
|
658
772
|
self.dn = attributes
|
659
773
|
elsif attributes.is_a?(Hash)
|
660
774
|
classes, attributes = extract_object_class(attributes)
|
661
|
-
apply_object_class(classes |
|
775
|
+
apply_object_class(classes | initial_classes)
|
662
776
|
normalized_attributes = {}
|
663
777
|
attributes.each do |key, value|
|
664
778
|
real_key = to_real_attribute_name(key)
|
@@ -666,6 +780,10 @@ module ActiveLdap
|
|
666
780
|
end
|
667
781
|
self.dn = normalized_attributes[dn_attribute]
|
668
782
|
self.attributes = normalized_attributes
|
783
|
+
else
|
784
|
+
message = "'#{attributes.inspect}' must be either "
|
785
|
+
message << "nil, DN value as String or Array or attributes as Hash"
|
786
|
+
raise ArgumentError, message
|
669
787
|
end
|
670
788
|
yield self if block_given?
|
671
789
|
end
|
@@ -750,6 +868,10 @@ module ActiveLdap
|
|
750
868
|
get_attribute(dn_attribute)
|
751
869
|
end
|
752
870
|
|
871
|
+
def to_param
|
872
|
+
id
|
873
|
+
end
|
874
|
+
|
753
875
|
def dn=(value)
|
754
876
|
set_attribute(dn_attribute, value)
|
755
877
|
end
|
@@ -834,7 +956,8 @@ module ActiveLdap
|
|
834
956
|
# Add available attributes to the methods
|
835
957
|
def methods(inherited_too=true)
|
836
958
|
ensure_apply_object_class
|
837
|
-
target_names = @attr_methods.keys + @attr_aliases.keys
|
959
|
+
target_names = @attr_methods.keys + @attr_aliases.keys
|
960
|
+
target_names -= ['objectClass', Inflector.underscore('objectClass')]
|
838
961
|
super + target_names.uniq.collect do |x|
|
839
962
|
[x, "#{x}=", "#{x}?", "#{x}_before_type_cast"]
|
840
963
|
end.flatten
|
@@ -946,15 +1069,13 @@ module ActiveLdap
|
|
946
1069
|
end
|
947
1070
|
|
948
1071
|
private
|
949
|
-
def logger
|
950
|
-
@@logger
|
951
|
-
end
|
952
|
-
|
953
1072
|
def extract_object_class(attributes)
|
954
1073
|
classes = []
|
955
1074
|
attrs = attributes.reject do |key, value|
|
956
|
-
|
957
|
-
|
1075
|
+
key = key.to_s
|
1076
|
+
if key == 'objectClass' or
|
1077
|
+
key.underscore == 'object_class' or
|
1078
|
+
key.downcase == 'objectclass'
|
958
1079
|
classes |= [value].flatten
|
959
1080
|
true
|
960
1081
|
else
|
@@ -980,10 +1101,18 @@ module ActiveLdap
|
|
980
1101
|
yield self if block_given?
|
981
1102
|
end
|
982
1103
|
|
983
|
-
def to_real_attribute_name(name)
|
1104
|
+
def to_real_attribute_name(name, allow_normalized_name=false)
|
984
1105
|
ensure_apply_object_class
|
985
1106
|
name = name.to_s
|
986
|
-
|
1107
|
+
real_name = @attr_methods[name]
|
1108
|
+
real_name ||= @attr_aliases[Inflector.underscore(name)]
|
1109
|
+
if real_name
|
1110
|
+
real_name
|
1111
|
+
elsif allow_normalized_name
|
1112
|
+
@attr_methods[@normalized_attr_names[normalize_attribute_name(name)]]
|
1113
|
+
else
|
1114
|
+
nil
|
1115
|
+
end
|
987
1116
|
end
|
988
1117
|
|
989
1118
|
def ensure_apply_object_class
|
@@ -1011,6 +1140,7 @@ module ActiveLdap
|
|
1011
1140
|
@ldap_data = {} # original ldap entry data
|
1012
1141
|
@attr_methods = {} # list of valid method calls for attributes used for
|
1013
1142
|
# dereferencing
|
1143
|
+
@normalized_attr_names = {} # list of normalized attribute name
|
1014
1144
|
@attr_aliases = {} # aliases of @attr_methods
|
1015
1145
|
@last_oc = false # for use in other methods for "caching"
|
1016
1146
|
@base = nil
|
@@ -1040,6 +1170,7 @@ module ActiveLdap
|
|
1040
1170
|
# Build |data| from schema
|
1041
1171
|
# clear attr_method mapping first
|
1042
1172
|
@attr_methods = {}
|
1173
|
+
@normalized_attr_names = {}
|
1043
1174
|
@attr_aliases = {}
|
1044
1175
|
@musts = {}
|
1045
1176
|
@mays = {}
|
@@ -1125,9 +1256,7 @@ module ActiveLdap
|
|
1125
1256
|
raise UnknownAttribute.new(name) if attr.nil?
|
1126
1257
|
|
1127
1258
|
if attr == dn_attribute and value.is_a?(String)
|
1128
|
-
value = value
|
1129
|
-
value, @base = value.split(/,/, 2)
|
1130
|
-
value = $POSTMATCH if /^#{dn_attribute}=/ =~ value
|
1259
|
+
value, @base = split_dn_value(value)
|
1131
1260
|
end
|
1132
1261
|
|
1133
1262
|
logger.debug {"set_attribute(#{name.inspect}, #{value.inspect}): " +
|
@@ -1154,6 +1283,24 @@ module ActiveLdap
|
|
1154
1283
|
@data[attr]
|
1155
1284
|
end
|
1156
1285
|
|
1286
|
+
def split_dn_value(value)
|
1287
|
+
dn_value = relative_dn_value = nil
|
1288
|
+
begin
|
1289
|
+
dn_value = DN.parse(value)
|
1290
|
+
rescue DistinguishedNameInvalid
|
1291
|
+
dn_value = DN.parse("#{dn_attribute}=#{value}")
|
1292
|
+
end
|
1293
|
+
|
1294
|
+
begin
|
1295
|
+
relative_dn_value = dn_value - DN.parse(base_of_class)
|
1296
|
+
relative_dn_value = dn_value if relative_dn_value.rdns.empty?
|
1297
|
+
rescue ArgumentError
|
1298
|
+
relative_dn_value = dn_value
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
val, *bases = relative_dn_value.rdns
|
1302
|
+
[val.values[0], bases.empty? ? nil : DN.new(*bases).to_s]
|
1303
|
+
end
|
1157
1304
|
|
1158
1305
|
# define_attribute_methods
|
1159
1306
|
#
|
@@ -1168,6 +1315,9 @@ module ActiveLdap
|
|
1168
1315
|
logger.debug {"associating #{Inflector.underscore(ali)}" +
|
1169
1316
|
" --> #{attr}"}
|
1170
1317
|
@attr_aliases[Inflector.underscore(ali)] = attr
|
1318
|
+
logger.debug {"associating #{normalize_attribute_name(ali)}" +
|
1319
|
+
" --> #{attr}"}
|
1320
|
+
@normalized_attr_names[normalize_attribute_name(ali)] = attr
|
1171
1321
|
end
|
1172
1322
|
logger.debug {"stub: leaving define_attribute_methods(#{attr.inspect})"}
|
1173
1323
|
end
|
@@ -1233,8 +1383,7 @@ module ActiveLdap
|
|
1233
1383
|
# Since some types do not have equality matching rules,
|
1234
1384
|
# delete doesn't work
|
1235
1385
|
# Replacing with nothing is equivalent.
|
1236
|
-
logger.debug {"#save: removing attribute from existing entry: "
|
1237
|
-
"#{new_key}"}
|
1386
|
+
logger.debug {"#save: removing attribute from existing entry: #{k}"}
|
1238
1387
|
if !data.has_key?(k) and schema.binary_required?(k)
|
1239
1388
|
value = [{'binary' => []}]
|
1240
1389
|
end
|
@@ -1340,13 +1489,9 @@ module ActiveLdap
|
|
1340
1489
|
prepare_data_for_saving do |data, ldap_data|
|
1341
1490
|
entries = collect_all_entries(data)
|
1342
1491
|
logger.debug {"#create: adding #{dn}"}
|
1343
|
-
|
1344
|
-
|
1345
|
-
|
1346
|
-
@new_entry = false
|
1347
|
-
rescue UnwillingToPerform
|
1348
|
-
logger.warn {"#create: didn't perform: #{$!.message}"}
|
1349
|
-
end
|
1492
|
+
self.class.add(dn, entries)
|
1493
|
+
logger.debug {"#create: add successful"}
|
1494
|
+
@new_entry = false
|
1350
1495
|
true
|
1351
1496
|
end
|
1352
1497
|
end
|