commit b0304d3003b2a568db3af0e21fa958a3ebdb509e
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Thu Jul 23 23:52:05 2009 +0200

    [evol] first experimental attempt for field modifications (with activeldap workaround for #26720)

diff --git a/bin/shadowwalker b/bin/shadowwalker
index 2d0d0b7..c200871 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -28,6 +28,7 @@ require 'cmdparse2'
 
 include LdapShadows
 
+
 config_str = IO.read(File.join(LdapShadows::Config::CFG_DIR, "test.conf"))
 config = YAML.load(config_str)
 config_str_prv_filelist = [
@@ -79,7 +80,7 @@ cmd.set_execution_block do |args|
     exit 1
   end
 
-  obj_hdl = args[0].singularize
+  obj_hdl = args.shift.singularize
   obj_klass = ldapctl.find_klass(obj_hdl)
   if obj_klass.nil?
     STDERR.puts "No such object '#{obj_hdl}'."
@@ -103,14 +104,14 @@ cmd.set_execution_block do |args|
     exit 1
   end
 
-  obj_hdl = args[0]
+  obj_hdl = args.shift
   obj_klass = ldapctl.find_klass(obj_hdl)
   if obj_klass.nil?
     STDERR.puts "No such object '#{obj_hdl}'."
     exit 2
   end
 
-  item_hdl = args[1]
+  item_hdl = args.shift
   begin
     item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
   rescue ActiveLdap::EntryNotFound
@@ -154,5 +155,50 @@ cmd.set_execution_block do |args|
 end
 cmdparser.add_command(cmd)
 
+cmd = CmdParse::Command.new('mod', false)
+cmd.short_desc = "Modify attributes of an object"
+cmd.set_execution_block do |args|
+  if args.size < 1
+    STDERR.puts "syntax error: no object name given"
+    exit 1
+  end
+  if args.size < 2
+    STDERR.puts "syntax error: no item name given"
+    exit 1
+  end
+
+  obj_hdl = args.shift
+  obj_klass = ldapctl.find_klass(obj_hdl)
+  if obj_klass.nil?
+    STDERR.puts "No such object '#{obj_hdl}'."
+    exit 2
+  end
+
+  item_hdl = args.shift
+  begin
+    item = obj_klass.find(item_hdl, :attributes => ["*", "+"])
+  rescue ActiveLdap::EntryNotFound
+    STDERR.puts "No such item '#{obj_hdl}/#{item_hdl}'"
+    exit 2
+  end
+
+  puts "COIN"
+  p args
+  args.each do |mod_info|
+    key, val = mod_info.split("=")
+    raise "Syntax error in modification parameters" if val.nil?
+
+    # TODO: manage relations (harder)
+    raise "No such field '#{key}' in object '#{obj_hdl}'" unless item.has_attribute?(key)
+
+    item[key] = val
+  end
+  p item.valid?
+  item.save!
+
+  Display.display_item(item, $program_options)
+end
+cmdparser.add_command(cmd)
+
 cmdparser.parse
 
diff --git a/lib/ldap_shadows/object.rb b/lib/ldap_shadows/object.rb
index 7fe36d3..753e0a9 100755
--- a/lib/ldap_shadows/object.rb
+++ b/lib/ldap_shadows/object.rb
@@ -30,11 +30,59 @@ module LdapShadows
       ActiveLdap::DistinguishedName.parse(self.dn)
     end
 
+    def self.find(*args)
+      obj = super(*args)
+      obj.load_initial_attribute_list
+      return obj
+    end
+
+    def load_initial_attribute_list
+      @initial_attribute_list ||= self.attributes.keys
+    end
+
     def name
       name = self[dn_attribute].is_a?(Array) ? self[dn_attribute][0] : self[dn_attribute]
       name.strip
     end
 
+    def validate_required_ldap_values
+      _schema = nil
+      # Make sure all MUST attributes have a value
+      entry_attribute.object_classes.each do |object_class|
+        object_class.must.each do |required_attribute|
+          # Normalize to ensure we catch schema problems
+          # needed?
+          real_name = to_real_attribute_name(required_attribute.name, true)
+          raise UnknownAttribute.new(required_attribute) if real_name.nil?
+
+          next if required_attribute.read_only?
+
+          value = @data[real_name] || []
+          next unless self.class.blank_value?(value)
+
+          # Duck: hack
+          next unless @initial_attribute_list.include?(real_name)
+
+          _schema ||= schema
+          aliases = required_attribute.aliases.collect do |name|
+            self.class.human_attribute_name(name)
+          end
+          args = [self.class.human_object_class_name(object_class)]
+          if aliases.empty?
+            format = _("%{fn} is required attribute by objectClass '%s'")
+          else
+            format = _("%{fn} is required attribute by objectClass " \
+                       "'%s': aliases: %s")
+            args << aliases.join(', ')
+          end
+          unless ActiveLdap.get_text_supported?
+            format = format.sub(/^%\{fn\} /, '')
+          end
+          errors.add(real_name, format % args)
+        end
+      end
+    end
+
     def human_name
       [self.class.presentation[:name_attribute], 'displayName', 'cn'].each do |attr|
         if attr == 'dn' or (self.has_attribute?(attr) and self.attribute_present?(attr))
