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.
Files changed (65) hide show
  1. data/CHANGES +5 -0
  2. data/Manifest.txt +91 -25
  3. data/README +22 -0
  4. data/Rakefile +41 -8
  5. data/TODO +1 -6
  6. data/examples/config.yaml.example +5 -0
  7. data/examples/example.der +0 -0
  8. data/examples/example.jpg +0 -0
  9. data/examples/groupadd +41 -0
  10. data/examples/groupdel +35 -0
  11. data/examples/groupls +49 -0
  12. data/examples/groupmod +42 -0
  13. data/examples/lpasswd +55 -0
  14. data/examples/objects/group.rb +13 -0
  15. data/examples/objects/ou.rb +4 -0
  16. data/examples/objects/user.rb +20 -0
  17. data/examples/ouadd +38 -0
  18. data/examples/useradd +45 -0
  19. data/examples/useradd-binary +50 -0
  20. data/examples/userdel +34 -0
  21. data/examples/userls +50 -0
  22. data/examples/usermod +42 -0
  23. data/examples/usermod-binary-add +47 -0
  24. data/examples/usermod-binary-add-time +51 -0
  25. data/examples/usermod-binary-del +48 -0
  26. data/examples/usermod-lang-add +43 -0
  27. data/lib/active_ldap.rb +213 -214
  28. data/lib/active_ldap/adapter/base.rb +461 -0
  29. data/lib/active_ldap/adapter/ldap.rb +232 -0
  30. data/lib/active_ldap/adapter/ldap_ext.rb +69 -0
  31. data/lib/active_ldap/adapter/net_ldap.rb +288 -0
  32. data/lib/active_ldap/adapter/net_ldap_ext.rb +29 -0
  33. data/lib/active_ldap/association/belongs_to.rb +3 -1
  34. data/lib/active_ldap/association/belongs_to_many.rb +5 -6
  35. data/lib/active_ldap/association/has_many.rb +9 -17
  36. data/lib/active_ldap/association/has_many_wrap.rb +4 -5
  37. data/lib/active_ldap/attributes.rb +4 -0
  38. data/lib/active_ldap/base.rb +201 -56
  39. data/lib/active_ldap/configuration.rb +11 -1
  40. data/lib/active_ldap/connection.rb +15 -9
  41. data/lib/active_ldap/distinguished_name.rb +246 -0
  42. data/lib/active_ldap/ldap_error.rb +74 -0
  43. data/lib/active_ldap/object_class.rb +9 -5
  44. data/lib/active_ldap/schema.rb +50 -9
  45. data/lib/active_ldap/validations.rb +11 -13
  46. data/rails/plugin/active_ldap/generators/scaffold_al/scaffold_al_generator.rb +7 -0
  47. data/rails/plugin/active_ldap/generators/scaffold_al/templates/ldap.yml +21 -0
  48. data/rails/plugin/active_ldap/init.rb +10 -4
  49. data/test/al-test-utils.rb +46 -3
  50. data/test/run-test.rb +16 -4
  51. data/test/test-unit-ext/always-show-result.rb +28 -0
  52. data/test/test-unit-ext/priority.rb +163 -0
  53. data/test/test_adapter.rb +81 -0
  54. data/test/test_attributes.rb +8 -1
  55. data/test/test_base.rb +132 -3
  56. data/test/test_base_per_instance.rb +14 -3
  57. data/test/test_connection.rb +19 -0
  58. data/test/test_dn.rb +161 -0
  59. data/test/test_find.rb +24 -0
  60. data/test/test_object_class.rb +15 -2
  61. data/test/test_schema.rb +108 -1
  62. metadata +111 -41
  63. data/lib/active_ldap/adaptor/base.rb +0 -29
  64. data/lib/active_ldap/adaptor/ldap.rb +0 -466
  65. 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
- filter = "(#{primary_key}=#{@owner[@options[:foreign_key_name]]})"
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
- filter = @owner[@options[:foreign_key_name], true].reject do |value|
32
- value.nil?
33
- end.collect do |value|
34
- "(#{key}=#{value})"
35
- end.join
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
- filter = @owner[@options[:foreign_key_name], true].collect do |value|
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.collect do |key, val|
25
- "(#{key}=#{val})"
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
- dn_attribute = foreign_class.dn_attribute
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.collect do |value|
36
- "(#{key}=#{value})"
37
- end.join
38
- filter = "(&#{filter})"
39
- entry_filter = entries.collect do |entry|
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
- filter = requested_targets.collect do |value|
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.collect do |key, val|
41
- "(#{key}=#{val})"
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 => "(|#{filter})").each do |target|
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
 
@@ -195,5 +195,9 @@ module ActiveLdap
195
195
  def attributes_protected_by_default
196
196
  [dn_attribute, 'objectClass']
197
197
  end
198
+
199
+ def normalize_attribute_name(name)
200
+ self.class.normalize_attribute_name(name)
201
+ end
198
202
  end
199
203
  end
@@ -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
- super("#{@dn} is invalid distinguished name (dn).")
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 UnwillingToPerform < Error
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, :classes, :scope]
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, :required_classes
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 ||= "(#{attr}=#{escape_filter_value(value, true)})"
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
- connection.search(:base => _base,
265
- :scope => options[:scope] || ldap_scope,
266
- :filter => filter,
267
- :limit => options[:limit],
268
- :attributes => options[:attributes]) do |dn, attrs|
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
- suffix = /,#{Regexp.escape(base)}$/
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) and suffix.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 /[=\(\)&\|]/ =~ filter
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
- search(options).collect do |dn, attrs|
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
- filter = "(#{attr || dn_attribute}=#{escape_filter_value(value, true)})"
523
- filter = "(&#{filter}#{options[:filter]})" if options[:filter]
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
- filter = "(|#{dn_filters.join('')})"
546
- filter = "(&#{filter}#{options[:filter]})" if options[:filter]
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
- value, prefix = value.split(/,/, 2)
559
- attr, value = value.split(/=/, 2)
560
- attr, value = value, attr if value.nil?
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(/^#{Regexp.escape(dn_attribute)}\s*=\s*/, '')
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.sub(/,#{Regexp.escape(base)}$/, '')
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
- "cn"
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
- if attributes.is_a?(String) or attributes.is_a?(Array)
657
- apply_object_class(required_classes)
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 | required_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 - ['objectClass']
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
- if key.to_s == 'objectClass' or
957
- Inflector.underscore(key) == 'object_class'
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
- @attr_methods[name] || @attr_aliases[Inflector.underscore(name)]
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.gsub(/,#{Regexp.escape(base_of_class)}$/, '')
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
- begin
1344
- self.class.add(dn, entries)
1345
- logger.debug {"#create: add successful"}
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