activeldap 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. data/CHANGES +15 -0
  2. data/README +4 -1
  3. data/Rakefile +1 -1
  4. data/TODO +1 -2
  5. data/data/locale/en/LC_MESSAGES/active-ldap.mo +0 -0
  6. data/data/locale/ja/LC_MESSAGES/active-ldap.mo +0 -0
  7. data/examples/al-admin/app/models/user.rb +1 -1
  8. data/examples/al-admin/config/boot.rb +1 -1
  9. data/examples/al-admin/config/environment.rb +1 -15
  10. data/examples/al-admin/config/initializers/session_store.rb +23 -0
  11. data/examples/al-admin/config/session_secret.txt +1 -0
  12. data/examples/al-admin/db/schema.rb +23 -0
  13. data/examples/al-admin/locale/en/LC_MESSAGES/al-admin.mo +0 -0
  14. data/examples/al-admin/locale/ja/LC_MESSAGES/al-admin.mo +0 -0
  15. data/examples/al-admin/locale/nl/LC_MESSAGES/al-admin.mo +0 -0
  16. data/lib/active_ldap.rb +3 -3
  17. data/lib/active_ldap/acts/tree.rb +1 -1
  18. data/lib/active_ldap/adapter/base.rb +9 -1
  19. data/lib/active_ldap/adapter/ldap.rb +7 -1
  20. data/lib/active_ldap/association/belongs_to_many.rb +6 -2
  21. data/lib/active_ldap/association/children.rb +1 -1
  22. data/lib/active_ldap/association/has_many_utils.rb +2 -2
  23. data/lib/active_ldap/association/has_many_wrap.rb +6 -2
  24. data/lib/active_ldap/attributes.rb +5 -1
  25. data/lib/active_ldap/base.rb +133 -59
  26. data/lib/active_ldap/connection.rb +10 -3
  27. data/lib/active_ldap/distinguished_name.rb +33 -1
  28. data/lib/active_ldap/ldif.rb +1 -0
  29. data/lib/active_ldap/operations.rb +33 -14
  30. data/lib/active_ldap/populate.rb +21 -12
  31. data/lib/active_ldap/schema.rb +21 -0
  32. data/lib/active_ldap/validations.rb +49 -4
  33. data/po/active-ldap.pot +4030 -0
  34. data/po/en/active-ldap.po +57 -37
  35. data/po/ja/active-ldap.po +58 -38
  36. data/test-unit/History.txt +1 -1
  37. data/test-unit/Manifest.txt +2 -0
  38. data/test-unit/README.txt +1 -1
  39. data/test-unit/lib/test/unit/assertions.rb +1 -1
  40. data/test-unit/lib/test/unit/autorunner.rb +19 -4
  41. data/test-unit/lib/test/unit/collector/load.rb +3 -1
  42. data/test-unit/lib/test/unit/color-scheme.rb +5 -1
  43. data/test-unit/lib/test/unit/error.rb +7 -5
  44. data/test-unit/lib/test/unit/runner/tap.rb +8 -0
  45. data/test-unit/lib/test/unit/ui/console/testrunner.rb +63 -8
  46. data/test-unit/lib/test/unit/ui/tap/testrunner.rb +92 -0
  47. data/test-unit/test/collector/test-load.rb +1 -5
  48. data/test-unit/test/test-color-scheme.rb +4 -0
  49. data/test/al-test-utils.rb +30 -2
  50. data/test/test_acts_as_tree.rb +6 -3
  51. data/test/test_associations.rb +3 -2
  52. data/test/test_base.rb +104 -5
  53. data/test/test_dn.rb +10 -0
  54. data/test/test_groupls.rb +1 -1
  55. data/test/test_lpasswd.rb +1 -1
  56. data/test/test_reflection.rb +23 -16
  57. data/test/test_schema.rb +33 -1
  58. data/test/test_useradd-binary.rb +1 -1
  59. data/test/test_useradd.rb +1 -1
  60. data/test/test_userdel.rb +1 -1
  61. data/test/test_userls.rb +1 -1
  62. data/test/test_usermod-binary-add-time.rb +1 -1
  63. data/test/test_usermod-binary-add.rb +1 -1
  64. data/test/test_usermod-binary-del.rb +1 -1
  65. data/test/test_usermod-lang-add.rb +1 -1
  66. data/test/test_usermod.rb +1 -1
  67. data/test/test_validation.rb +48 -10
  68. metadata +44 -35
data/CHANGES CHANGED
@@ -1,5 +1,20 @@
1
1
  = CHANGES
2
2
 
3
+ == 1.2.0: 2009-09-22
4
+
5
+ * Supported ActiveRecord 2.3.4 and Rails 2.3.4.
6
+ * [IMCOMPATIBLE]
7
+ [#23932] Inconsistant DN handling in object attributes [Marc Dequènes]
8
+ (ActiveLdap::Base#dn and ActiveLdap::Base#base return
9
+ ActiveLdap::DN not String)
10
+ * [#26824] support operational attributes detection [Marc Dequènes]
11
+ (added ActiveLdap::Schema::Attribute#directory_operation?)
12
+ * [#27] Error saving an ActiveLDAP user [brad@lucky-dip.net]
13
+ * [#29] Raised on modify_rdn_entry when rdn already exists [Alexey.Chebotar]
14
+ * Added ActiveLdap::DN.parent.
15
+ * Supported renaming an entry. Renaming other DTI is only supported by
16
+ JNDI backend.
17
+
3
18
  == 1.1.0: 2009-07-18
4
19
 
5
20
  * Improved tutorial. [Kazuaki Takase]
data/README CHANGED
@@ -133,4 +133,7 @@ list, please point out.
133
133
  * Tim Hermans: A bug report.
134
134
  * Joe Francis: A suggestion.
135
135
  * Tiago Fernandes: Bug reports.
136
- * achemze. A suggestion.
136
+ * achemze: A suggestion.
137
+ * George Montana Harkin: A suggestion.
138
+ * Marc Dequènes: Bug reports.
139
+ * brad@lucky-dip.net: A bug report.
data/Rakefile CHANGED
@@ -65,7 +65,7 @@ project = Hoe.spec('activeldap') do
65
65
  self.changes = self.paragraphs_of('CHANGES', 1..2).join("\n\n")
66
66
  self.extra_deps = [
67
67
  # ['ruby-ldap', '= 0.9.9'],
68
- ['activerecord', '= 2.3.2'],
68
+ ['activerecord', '= 2.3.4'],
69
69
  ['locale', '= 2.0.4'],
70
70
  ['gettext', '= 2.0.4'],
71
71
  ['gettext_activerecord', '= 2.0.4'],
data/TODO CHANGED
@@ -1,6 +1,5 @@
1
- - [1.1.1] add :readonly option to :has_many.
1
+ - [1.1.x] add :readonly option to :has_many.
2
2
  - Work as framework on Rails.
3
- - [1.1.x] ActiveLdap::Base#dn returns ActiveLdap::DN. [#23932] [Marc Dequènes]
4
3
  - How to support dSCorePropagationData? ignore it?
5
4
  all systemOnly == "TRUE" attribute can be ignored?
6
5
  - Add parsing position to DistinguishedNameInvalid error like
@@ -82,7 +82,7 @@ class User < ActiveRecord::Base
82
82
  else
83
83
  begin
84
84
  ldap_user = LdapUser.find(login)
85
- self.dn = ldap_user.dn
85
+ self.dn = ldap_user.dn.to_s
86
86
  rescue ActiveLdap::EntryNotFound
87
87
  self.dn = nil
88
88
  end
@@ -82,8 +82,8 @@ module Rails
82
82
  end
83
83
 
84
84
  def load_rubygems
85
+ min_version = '1.3.2'
85
86
  require 'rubygems'
86
- min_version = '1.3.1'
87
87
  unless rubygems_version >= min_version
88
88
  $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
89
89
  exit 1
@@ -5,7 +5,7 @@
5
5
  # ENV['RAILS_ENV'] ||= 'production'
6
6
 
7
7
  # Specifies gem version of Rails to use when vendor/rails is not present
8
- RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION
8
+ RAILS_GEM_VERSION = '2.3.4' unless defined? RAILS_GEM_VERSION
9
9
 
10
10
  # Bootstrap the Rails environment, frameworks, and default configuration
11
11
  require File.join(File.dirname(__FILE__), 'boot')
@@ -55,20 +55,6 @@ Rails::Initializer.run do |config|
55
55
  # config.i18n.load_path << Dir[File.join(RAILS_ROOT, 'my', 'locales', '*.{rb,yml}')]
56
56
  # config.i18n.default_locale = :de
57
57
 
58
- # Your secret key for verifying cookie session data integrity.
59
- # If you change this key, all old sessions will become invalid!
60
- # Make sure the secret is at least 30 characters and all random,
61
- # no regular words or you'll be exposed to dictionary attacks.
62
- config.action_controller.session = {
63
- :session_key => '_al_admin_session',
64
- :secret => '581ed74d26a88caa7cb2ff6d0ea0f0aeea1a49f98641ee6d3e7ba1dfcf6154e26cd2b3f9636c9cc02ed8139a4f74c64fdb529a53dcfd1b7ff7aa763f91083aad'
65
- }
66
-
67
- # Use the database for sessions instead of the cookie-based default,
68
- # which shouldn't be used to store highly confidential information
69
- # (create the session table with "rake db:sessions:create")
70
- # config.action_controller.session_store = :active_record_store
71
-
72
58
  # Use SQL instead of Active Record's schema dumper when creating the test database.
73
59
  # This is necessary if your schema can't be completely dumped by the schema dumper,
74
60
  # like if you have constraints or database-specific column types
@@ -0,0 +1,23 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Your secret key for verifying cookie session data integrity.
4
+ # If you change this key, all old sessions will become invalid!
5
+ # Make sure the secret is at least 30 characters and all random,
6
+ # no regular words or you'll be exposed to dictionary attacks.
7
+ session_secret_file = File.join(RAILS_ROOT, "config", "session_secret.txt")
8
+ unless File.exist?(session_secret_file)
9
+ File.open(session_secret_file, "w") do |file|
10
+ file.puts(ActiveSupport::SecureRandom.hex(64))
11
+ end
12
+ end
13
+ session_secret = File.read(session_secret_file).strip
14
+
15
+ ActionController::Base.session = {
16
+ :key => '_al_admin_session',
17
+ :secret => session_secret,
18
+ }
19
+
20
+ # Use the database for sessions instead of the cookie-based default,
21
+ # which shouldn't be used to store highly confidential information
22
+ # (create the session table with "rake db:sessions:create")
23
+ # ActionController::Base.session_store = :active_record_store
@@ -0,0 +1 @@
1
+ 455ed531e0b1f453d98bdf1bffc9941d659ae54b8ed7506fa63edab05c04516f8888540d4ef6870fa660fe525668f59ebfcc2408446f17e57fa41243e6edb0c6
@@ -0,0 +1,23 @@
1
+ # This file is auto-generated from the current state of the database. Instead of editing this file,
2
+ # please use the migrations feature of Active Record to incrementally modify your database, and
3
+ # then regenerate this schema definition.
4
+ #
5
+ # Note that this schema.rb definition is the authoritative source for your database schema. If you need
6
+ # to create the application database on another system, you should be using db:schema:load, not running
7
+ # all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
8
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
9
+ #
10
+ # It's strongly recommended to check this file into your version control system.
11
+
12
+ ActiveRecord::Schema.define(:version => 1) do
13
+
14
+ create_table "users", :force => true do |t|
15
+ t.string "login"
16
+ t.string "dn"
17
+ t.datetime "updated_at"
18
+ t.string "salt"
19
+ t.string "remember_token"
20
+ t.datetime "remember_token_expires_at"
21
+ end
22
+
23
+ end
@@ -901,7 +901,7 @@ require_gem_if_need = Proc.new do |library_name, gem_name, *gem_args|
901
901
  end
902
902
  end
903
903
 
904
- require_gem_if_need.call("active_support", "activesupport", "= 2.3.2")
904
+ require_gem_if_need.call("active_support", "activesupport", "= 2.3.4")
905
905
 
906
906
  if ActiveSupport.const_defined?(:Dependencies)
907
907
  dependencies = ActiveSupport::Dependencies
@@ -914,7 +914,7 @@ if dependencies.respond_to?(:load_paths)
914
914
  end
915
915
 
916
916
  module ActiveLdap
917
- VERSION = "1.1.0"
917
+ VERSION = "1.2.0"
918
918
  end
919
919
 
920
920
  if RUBY_PLATFORM.match('linux')
@@ -923,7 +923,7 @@ else
923
923
  require 'active_ldap/timeout_stub'
924
924
  end
925
925
 
926
- require_gem_if_need.call("active_record", "activerecord", "= 2.3.2")
926
+ require_gem_if_need.call("active_record", "activerecord", "= 2.3.4")
927
927
  begin
928
928
  require_gem_if_need.call("locale", nil, "= 2.0.4")
929
929
  require_gem_if_need.call("gettext", nil, "= 2.0.4")
@@ -55,7 +55,7 @@ module ActiveLdap
55
55
  end
56
56
 
57
57
  def parent=(entry)
58
- if entry.is_a?(String)
58
+ if entry.is_a?(String) or entry.is_a?(DN)
59
59
  base = entry
60
60
  elsif entry.respond_to?(:dn)
61
61
  base = entry.dn
@@ -139,6 +139,10 @@ module ActiveLdap
139
139
  end
140
140
  end
141
141
 
142
+ def naming_contexts
143
+ root_dse_values('namingContexts')
144
+ end
145
+
142
146
  def entry_attribute(object_classes)
143
147
  @entry_attributes[object_classes.uniq.sort] ||=
144
148
  EntryAttribute.new(schema, object_classes)
@@ -615,7 +619,11 @@ module ActiveLdap
615
619
  def root_dse_values(key, options={})
616
620
  dse = root_dse([key], options)[0]
617
621
  return [] if dse.nil?
618
- dse[key] || dse[key.downcase] || []
622
+ normalized_key = key.downcase
623
+ dse.each do |_key, _value|
624
+ return _value if _key.downcase == normalized_key
625
+ end
626
+ []
619
627
  end
620
628
 
621
629
  def root_dse(attrs, options={})
@@ -158,9 +158,15 @@ module ActiveLdap
158
158
 
159
159
  def modify_rdn(dn, new_rdn, delete_old_rdn, new_superior, options={})
160
160
  super do |_dn, _new_rdn, _delete_old_rdn, _new_superior|
161
+ if _new_superior
162
+ raise NotImplemented.new(_("modify RDN with new superior"))
163
+ end
161
164
  info = {
162
165
  :name => "modify: RDN",
163
- :dn => _dn, :new_rdn => _new_rdn, :delete_old_rdn => _delete_old_rdn
166
+ :dn => _dn,
167
+ :new_rdn => _new_rdn,
168
+ :new_superior => _new_superior,
169
+ :delete_old_rdn => _delete_old_rdn
164
170
  }
165
171
  execute(:modrdn, info, _dn, _new_rdn, _delete_old_rdn)
166
172
  end
@@ -10,7 +10,9 @@ module ActiveLdap
10
10
  if primary_key_name == "dn"
11
11
  old_value = dn_values_to_string_values(old_value)
12
12
  end
13
- new_value = old_value + @owner[primary_key_name, true]
13
+ current_value = @owner[primary_key_name, true]
14
+ current_value = dn_values_to_string_values(current_value)
15
+ new_value = old_value + current_value
14
16
  new_value = new_value.uniq.sort
15
17
  if old_value != new_value
16
18
  entry[@options[:many]] = new_value
@@ -25,7 +27,9 @@ module ActiveLdap
25
27
  if primary_key_name == "dn"
26
28
  old_value = dn_values_to_string_values(old_value)
27
29
  end
28
- new_value = old_value - @owner[primary_key_name, true]
30
+ current_value = @owner[primary_key_name, true]
31
+ current_value = dn_values_to_string_values(current_value)
32
+ new_value = old_value - current_value
29
33
  new_value = new_value.uniq.sort
30
34
  if old_value != new_value
31
35
  entry[@options[:many]] = new_value
@@ -5,7 +5,7 @@ module ActiveLdap
5
5
  class Children < Collection
6
6
  private
7
7
  def insert_entry(entry)
8
- entry.dn = [entry.id, @owner.dn].join(",")
8
+ entry.dn = [entry.id, @owner.dn.to_s].join(",")
9
9
  entry.save
10
10
  end
11
11
 
@@ -14,9 +14,9 @@ module ActiveLdap
14
14
  elsif foreign_base_key == "dn"
15
15
  requested_targets = requested_targets.collect do |target|
16
16
  if target.is_a?(DN)
17
- target.to_s
18
- else
19
17
  target
18
+ else
19
+ DN.parse(target)
20
20
  end
21
21
  end
22
22
  targets = []
@@ -13,7 +13,9 @@ module ActiveLdap
13
13
  if _primary_key == "dn"
14
14
  old_value = dn_values_to_string_values(old_value)
15
15
  end
16
- new_value = (old_value + entry[_primary_key, true]).uniq.sort
16
+ current_value = entry[_primary_key, true]
17
+ current_value = dn_values_to_string_values(current_value)
18
+ new_value = (old_value + current_value).uniq.sort
17
19
  if old_value != new_value
18
20
  @owner[@options[:wrap]] = new_value
19
21
  @owner.save
@@ -26,7 +28,9 @@ module ActiveLdap
26
28
  if _primary_key == "dn"
27
29
  old_value = dn_values_to_string_values(old_value)
28
30
  end
29
- new_value = old_value - entries.collect {|entry| entry[_primary_key]}
31
+ current_value = entries.collect {|entry| entry[_primary_key]}
32
+ current_value = dn_values_to_string_values(current_value)
33
+ new_value = old_value - current_value
30
34
  new_value = new_value.uniq.sort
31
35
  if old_value != new_value
32
36
  @owner[@options[:wrap]] = new_value
@@ -105,7 +105,11 @@ module ActiveLdap
105
105
  result[new_name].concat(real_value)
106
106
  else
107
107
  result[name] ||= []
108
- result[name] << value.dup
108
+ if value.is_a?(DN)
109
+ result[name] << value.to_s
110
+ else
111
+ result[name] << value.dup
112
+ end
109
113
  end
110
114
  end
111
115
  end
@@ -259,6 +259,14 @@ module ActiveLdap
259
259
  end
260
260
  end
261
261
 
262
+ class NotImplemented < Error
263
+ attr_reader :target
264
+ def initialize(target)
265
+ @target = target
266
+ super(_("not implemented: %s") % @target)
267
+ end
268
+ end
269
+
262
270
  # Base
263
271
  #
264
272
  # Base is the primary class which contains all of the core
@@ -313,7 +321,7 @@ module ActiveLdap
313
321
  end
314
322
  end
315
323
 
316
- class_local_attr_accessor false, :prefix, :base
324
+ class_local_attr_accessor false, :inheritable_prefix, :inheritable_base
317
325
  class_local_attr_accessor true, :dn_attribute, :scope, :sort_by, :order
318
326
  class_local_attr_accessor true, :required_classes, :recommended_classes
319
327
  class_local_attr_accessor true, :excluded_classes
@@ -412,7 +420,6 @@ module ActiveLdap
412
420
  public_class_method :new
413
421
  end
414
422
 
415
- alias_method :base_inheritable, :base
416
423
  # Base.base
417
424
  #
418
425
  # This method when included into Base provides
@@ -424,22 +431,23 @@ module ActiveLdap
424
431
  # configuration.rb into this class.
425
432
  # When subclassing, the specified prefix will be concatenated.
426
433
  def base
427
- _base = base_inheritable
428
- _base = configuration[:base] if _base.nil? and configuration
429
- _base ||= base_inheritable(true)
430
- [prefix, _base].find_all do |component|
431
- !component.blank?
432
- end.join(",")
434
+ @base ||= compute_base
433
435
  end
436
+ alias_method :parsed_base, :base # for backward compatibility
434
437
 
435
- alias_method :base_without_parsed_cache_clear=, :base=
436
438
  def base=(value)
437
- self.base_without_parsed_cache_clear = value
438
- @parsed_base = nil
439
+ self.inheritable_base = value
440
+ @base = nil
439
441
  end
440
442
 
441
- def parsed_base
442
- @parsed_base ||= DN.parse(base)
443
+ def prefix
444
+ @prefix ||= inheritable_prefix and DN.parse(inheritable_prefix)
445
+ end
446
+
447
+ def prefix=(value)
448
+ self.inheritable_prefix = value
449
+ @prefix = nil
450
+ @base = nil
443
451
  end
444
452
 
445
453
  alias_method :scope_without_validation=, :scope=
@@ -473,17 +481,23 @@ module ActiveLdap
473
481
  elsif abstract_class?
474
482
  "#{super}(abstract)"
475
483
  else
476
- class_names = []
477
- must = []
478
- may = []
479
- class_names = classes.collect do |object_class|
480
- must.concat(object_class.must)
481
- may.concat(object_class.may)
482
- object_class.name
484
+ detail = nil
485
+ begin
486
+ must = []
487
+ may = []
488
+ class_names = classes.collect do |object_class|
489
+ must.concat(object_class.must)
490
+ may.concat(object_class.may)
491
+ object_class.name
492
+ end
493
+ detail = ["objectClass:<#{class_names.join(', ')}>",
494
+ "must:<#{inspect_attributes(must)}>",
495
+ "may:<#{inspect_attributes(may)}>"].join(", ")
496
+ rescue ActiveLdap::ConnectionNotSetup
497
+ detail = "not-connected"
498
+ rescue ActiveLdap::Error
499
+ detail = "connection-failure"
483
500
  end
484
- detail = ["objectClass:<#{class_names.join(', ')}>",
485
- "must:<#{inspect_attributes(must)}>",
486
- "may:<#{inspect_attributes(may)}>"].join(", ")
487
501
  "#{super}(#{detail})"
488
502
  end
489
503
  end
@@ -615,6 +629,28 @@ module ActiveLdap
615
629
  "ou=#{name.demodulize.pluralize}"
616
630
  end
617
631
  end
632
+
633
+ def compute_base
634
+ _base = inheritable_base
635
+ _base = configuration[:base] if _base.nil? and configuration
636
+ if _base.nil?
637
+ target = superclass
638
+ loop do
639
+ break unless target.respond_to?(:base)
640
+ _base = target.base
641
+ break if _base
642
+ target = target.superclass
643
+ end
644
+ end
645
+ _prefix = prefix
646
+
647
+ _base ||= connection.naming_contexts.first
648
+ return _prefix if _base.blank?
649
+
650
+ _base = DN.parse(_base)
651
+ _base = _prefix + _base if _prefix
652
+ _base
653
+ end
618
654
  end
619
655
 
620
656
  self.scope = :sub
@@ -763,12 +799,8 @@ module ActiveLdap
763
799
  #
764
800
  # Delete this entry from LDAP
765
801
  def destroy
766
- begin
767
- self.class.delete(dn)
768
- @new_entry = true
769
- rescue Error
770
- raise DeleteError.new(_("Failed to delete LDAP entry: %s") % dn)
771
- end
802
+ self.class.delete(dn)
803
+ @new_entry = true
772
804
  end
773
805
 
774
806
  def delete(options={})
@@ -844,9 +876,9 @@ module ActiveLdap
844
876
  return true if super
845
877
 
846
878
  name = name.to_s
847
- return true if have_attribute?(name)
879
+ return true if have_attribute?(name, ["objectClass"])
848
880
  return false if /(?:=|\?|_before_type_cast)$/ !~ name
849
- have_attribute?($PREMATCH)
881
+ have_attribute?($PREMATCH, ["objectClass"])
850
882
  end
851
883
 
852
884
  # Updates a given attribute and saves immediately
@@ -870,7 +902,7 @@ module ActiveLdap
870
902
  # This returns the key value pairs in @data with all values
871
903
  # cloned
872
904
  def attributes
873
- Marshal.load(Marshal.dump(@data))
905
+ @data.clone
874
906
  end
875
907
 
876
908
  # This allows a bulk update to the attributes of a record
@@ -1006,18 +1038,15 @@ module ActiveLdap
1006
1038
  @schema ||= super
1007
1039
  end
1008
1040
 
1009
- alias_method :base_of_class, :base
1010
1041
  def base
1011
- ensure_update_dn
1012
- [@base, base_of_class].find_all do |component|
1013
- not component.blank?
1014
- end.join(",")
1042
+ @base ||= compute_base
1015
1043
  end
1016
1044
 
1017
1045
  def base=(object_local_base)
1018
1046
  ensure_update_dn
1019
1047
  @dn = nil
1020
- @base = object_local_base
1048
+ @base = nil
1049
+ @base_value = object_local_base
1021
1050
  end
1022
1051
 
1023
1052
  alias_method :scope_of_class, :scope
@@ -1030,6 +1059,14 @@ module ActiveLdap
1030
1059
  @scope = scope
1031
1060
  end
1032
1061
 
1062
+ def delete_all(options={})
1063
+ super({:base => dn}.merge(options || {}))
1064
+ end
1065
+
1066
+ def destroy_all(options={})
1067
+ super({:base => dn}.merge(options || {}))
1068
+ end
1069
+
1033
1070
  def inspect
1034
1071
  object_classes = entry_attribute.object_classes
1035
1072
  inspected_object_classes = object_classes.collect do |object_class|
@@ -1106,7 +1143,10 @@ module ActiveLdap
1106
1143
  init_base
1107
1144
  dn = Compatible.convert_to_utf8_encoded_object(dn)
1108
1145
  attributes = Compatible.convert_to_utf8_encoded_object(attributes)
1146
+ @original_dn = dn.clone
1109
1147
  @dn = dn
1148
+ @base = nil
1149
+ @base_value = nil
1110
1150
  @new_entry = false
1111
1151
  @dn_is_base = false
1112
1152
  @ldap_data = attributes
@@ -1259,6 +1299,7 @@ module ActiveLdap
1259
1299
  if new_name.nil? and new_value.nil?
1260
1300
  @dn_is_base = true
1261
1301
  @base = nil
1302
+ @base_value = nil
1262
1303
  attr, value = bases[0].to_a[0]
1263
1304
  @dn_attribute = attr
1264
1305
  else
@@ -1271,10 +1312,11 @@ module ActiveLdap
1271
1312
  new_bases = bases.empty? ? nil : DN.new(*bases).to_s
1272
1313
  dn_components = ["#{new_name}=#{new_value}",
1273
1314
  new_bases,
1274
- base_of_class]
1315
+ self.class.base.to_s]
1275
1316
  dn_components = dn_components.find_all {|component| !component.blank?}
1276
1317
  DN.parse(dn_components.join(','))
1277
- @base = new_bases
1318
+ @base = nil
1319
+ @base_value = new_bases
1278
1320
  @dn_attribute = new_name
1279
1321
  end
1280
1322
  end
@@ -1294,7 +1336,9 @@ module ActiveLdap
1294
1336
 
1295
1337
  val = bases = nil
1296
1338
  begin
1297
- relative_dn_value = dn_value - self.class.parsed_base
1339
+ relative_dn_value = dn_value
1340
+ base_of_class = self.class.base
1341
+ relative_dn_value -= base_of_class if base_of_class
1298
1342
  if relative_dn_value.rdns.empty?
1299
1343
  val = []
1300
1344
  bases = dn_value.rdns
@@ -1328,7 +1372,7 @@ module ActiveLdap
1328
1372
  end
1329
1373
  end
1330
1374
 
1331
- def compute_dn(escape_dn_value=false)
1375
+ def compute_dn
1332
1376
  return base if @dn_is_base
1333
1377
 
1334
1378
  ensure_update_dn
@@ -1338,14 +1382,21 @@ module ActiveLdap
1338
1382
  message = format % [self.inspect, dn_attribute]
1339
1383
  raise DistinguishedNameNotSetError.new, message
1340
1384
  end
1341
- dn_value = DN.escape_value(dn_value.to_s) if escape_dn_value
1385
+ dn_value = DN.escape_value(dn_value.to_s)
1342
1386
  _base = base
1343
1387
  _base = nil if _base.blank?
1344
- ["#{dn_attribute}=#{dn_value}", _base].compact.join(",")
1388
+ DN.parse(["#{dn_attribute}=#{dn_value}", _base].compact.join(","))
1345
1389
  end
1346
1390
 
1347
- def escaped_dn
1348
- compute_dn(true)
1391
+ def compute_base
1392
+ base_of_class = self.class.base
1393
+ if @base_value.nil?
1394
+ base_of_class
1395
+ else
1396
+ base_of_object = DN.parse(@base_value)
1397
+ base_of_object += base_of_class if base_of_class
1398
+ base_of_object
1399
+ end
1349
1400
  end
1350
1401
 
1351
1402
  # array_of
@@ -1393,7 +1444,11 @@ module ActiveLdap
1393
1444
  end
1394
1445
 
1395
1446
  def collect_modified_attributes(ldap_data, data)
1447
+ klass = self.class
1448
+ _dn_attribute = dn_attribute
1449
+ new_dn_value = nil
1396
1450
  attributes = []
1451
+
1397
1452
  # Now that all the options will be treated as unique attributes
1398
1453
  # we can see what's changed and add anything that is brand-spankin'
1399
1454
  # new.
@@ -1402,23 +1457,26 @@ module ActiveLdap
1402
1457
 
1403
1458
  next if v == value
1404
1459
 
1405
- x = value
1406
- value = self.class.remove_blank_value(value) || []
1460
+ value = klass.remove_blank_value(value) || []
1407
1461
  next if v == value
1408
1462
 
1409
- if self.class.blank_value?(value) and
1463
+ if klass.blank_value?(value) and
1410
1464
  schema.attribute(k).binary_required?
1411
1465
  value = [{'binary' => []}]
1412
1466
  end
1413
- attributes.push([:replace, k, value])
1467
+ if k == _dn_attribute
1468
+ new_dn_value = value[0]
1469
+ else
1470
+ attributes.push([:replace, k, value])
1471
+ end
1414
1472
  end
1473
+
1415
1474
  data.each do |k, v|
1416
1475
  value = v || []
1417
1476
  next if ldap_data.has_key?(k)
1418
1477
 
1419
- value = self.class.remove_blank_value(value) || []
1420
- next if self.class.blank_value?(value)
1421
-
1478
+ value = klass.remove_blank_value(value) || []
1479
+ next if klass.blank_value?(value)
1422
1480
 
1423
1481
  # Detect subtypes and account for them
1424
1482
  # REPLACE will function like ADD, but doesn't hit EQUALITY problems
@@ -1426,7 +1484,7 @@ module ActiveLdap
1426
1484
  attributes.push([:replace, k, value])
1427
1485
  end
1428
1486
 
1429
- attributes
1487
+ [new_dn_value, attributes]
1430
1488
  end
1431
1489
 
1432
1490
  def collect_all_attributes(data)
@@ -1460,18 +1518,21 @@ module ActiveLdap
1460
1518
  ldap_data = normalize_data(@ldap_data)
1461
1519
 
1462
1520
  # Expand subtypes to real data attributes, but leave @data alone
1463
- bad_attrs = @data.keys - attribute_names
1521
+ original_attributes =
1522
+ connection.entry_attribute(@ldap_data["objectClass"] || []).names
1523
+ bad_attrs = original_attributes - entry_attribute.names
1464
1524
  data = normalize_data(@data, bad_attrs)
1465
1525
 
1466
1526
  success = yield(data, ldap_data)
1467
1527
 
1468
1528
  if success
1469
- @ldap_data = Marshal.load(Marshal.dump(data))
1529
+ @ldap_data = data.clone
1470
1530
  # Delete items disallowed by objectclasses.
1471
1531
  # They should have been removed from ldap.
1472
1532
  bad_attrs.each do |remove_me|
1473
1533
  @ldap_data.delete(remove_me)
1474
1534
  end
1535
+ @original_dn = dn.clone
1475
1536
  end
1476
1537
 
1477
1538
  success
@@ -1480,7 +1541,7 @@ module ActiveLdap
1480
1541
  def create
1481
1542
  prepare_data_for_saving do |data, ldap_data|
1482
1543
  attributes = collect_all_attributes(data)
1483
- add_entry(escaped_dn, attributes)
1544
+ add_entry(dn, attributes)
1484
1545
  @new_entry = false
1485
1546
  true
1486
1547
  end
@@ -1488,8 +1549,21 @@ module ActiveLdap
1488
1549
 
1489
1550
  def update
1490
1551
  prepare_data_for_saving do |data, ldap_data|
1491
- attributes = collect_modified_attributes(ldap_data, data)
1492
- modify_entry(escaped_dn, attributes)
1552
+ new_dn_value, attributes = collect_modified_attributes(ldap_data, data)
1553
+ modify_entry(@original_dn, attributes)
1554
+ if new_dn_value
1555
+ old_dn_base = DN.parse(@original_dn).parent
1556
+ new_dn_base = dn.clone.parent
1557
+ if old_dn_base == new_dn_base
1558
+ new_superior = nil
1559
+ else
1560
+ new_superior = new_dn_base
1561
+ end
1562
+ modify_rdn_entry(@original_dn,
1563
+ "#{dn_attribute}=#{DN.escape_value(new_dn_value)}",
1564
+ true,
1565
+ new_superior)
1566
+ end
1493
1567
  true
1494
1568
  end
1495
1569
  end