ruby-activeldap 0.8.1 → 0.8.2
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/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
|