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.
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