[Pkg-puppet-devel] [SCM] Puppet packaging for Debian branch, master, updated. debian/0.24.6-1-356-g5718585

James Turnbull james at lovedthanlost.net
Fri Jan 23 14:21:41 UTC 2009


The following commit has been merged in the master branch:
commit 8523a483155eccc543dd7d17ea8c4f942dcc249f
Author: James Turnbull <james at lovedthanlost.net>
Date:   Wed Nov 19 18:49:50 2008 +1100

    Fixed #1751 - Mac OS X DirectoryService nameservice provider support for plist output and password hash fil

diff --git a/CHANGELOG b/CHANGELOG
index 00392f0..7f2d645 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,7 @@
 0.24.x
+    Fixed #1751 - Mac OS X DirectoryService nameservice provider support for 
+    plist output and password hash fil 
+   
     Fixed #1752 - Add an optional argument to Puppet::Util.execute to determine 
     whether stderr and stdout are combined in the output
     
diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb
index fcc44f9..a20a8a9 100644
--- a/lib/puppet/provider/nameservice/directoryservice.rb
+++ b/lib/puppet/provider/nameservice/directoryservice.rb
@@ -14,6 +14,7 @@
 
 require 'puppet'
 require 'puppet/provider/nameservice'
+require 'facter/util/plist'
 
 class Puppet::Provider::NameService
 class DirectoryService < Puppet::Provider::NameService
@@ -37,8 +38,7 @@ class DirectoryService < Puppet::Provider::NameService
     
     commands :dscl => "/usr/bin/dscl"
     confine :operatingsystem => :darwin
-    # JJM FIXME: This will need to be the default around October 2007.
-    # defaultfor :operatingsystem => :darwin
+    defaultfor :operatingsystem => :darwin
 
 
     # JJM 2007-07-25: This map is used to map NameService attributes to their
@@ -55,6 +55,7 @@ class DirectoryService < Puppet::Provider::NameService
         'UniqueID' => :uid,
         'RealName' => :comment,
         'Password' => :password,
+        'GeneratedUID' => :guid,
     }
     # JJM The same table as above, inverted.
     @@ns_to_ds_attribute_map = {
@@ -65,16 +66,16 @@ class DirectoryService < Puppet::Provider::NameService
         :uid => 'UniqueID',
         :comment => 'RealName',
         :password => 'Password',
+        :guid => 'GeneratedUID',
     }
     
+    @@password_hash_dir = "/var/db/shadow/hash"
+    
     def self.instances
         # JJM Class method that provides an array of instance objects of this
         #     type.
-        
         # JJM: Properties are dependent on the Puppet::Type we're managine.
         type_property_array = [:name] + @resource_type.validproperties
-        # JJM: No sense reporting the password.  It's hashed.
-        type_property_array.delete(:password) if type_property_array.include? :password
         
         # Create a new instance of this Puppet::Type for each object present
         #    on the system.
@@ -119,7 +120,7 @@ class DirectoryService < Puppet::Provider::NameService
         
         all_present_str_array = list_all_present()
         
-        # JJM: Return nil if the named object isn't present.
+        # NBK: shortcut the process if the resource is missing
         return nil unless all_present_str_array.include? resource_name
         
         dscl_vector = get_exec_preamble("-read", resource_name)
@@ -132,44 +133,20 @@ class DirectoryService < Puppet::Provider::NameService
         # JJM: We need a new hash to return back to our caller.
         attribute_hash = Hash.new
         
-        # JJM: First, the output string goes into an array.
-        #      Then, the each array element is split
-        #      If you want to figure out what this is doing, I suggest
-        #      ruby-debug, and stepping through it.
-        dscl_output.split("\n").each do |line|
-            # JJM: Split the attribute name and the list of values.
-            ds_attribute, ds_values_string = line.split(':')
-
-            # Split sets the values to nil if there's nothing after the :
-            ds_values_string ||= ""
-            
-            # JJM: skip this attribute line if the Puppet::Type doesn't care about it.
+        dscl_plist = Plist.parse_xml(dscl_output)
+        dscl_plist.keys().each do |key|
+            ds_attribute = key.sub("dsAttrTypeStandard:", "")
             next unless (@@ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? @@ds_to_ns_attribute_map[ds_attribute])
-
-            # JJM: We asked dscl to output url encoded values so we're able
-            #     to machine parse on whitespace.  We need to urldecode:
-            #     " Jeff%20McCune John%20Doe " => ["Jeff McCune", "John Doe"]
-            ds_value_array = ds_values_string.scan(/[^\s]+/).collect do |v|
-                url_decoded_value = CGI::unescape v
-                if url_decoded_value =~ /^[-0-9]+$/
-                    url_decoded_value.to_i
-                else                
-                    url_decoded_value
-                end
-            end
-            
-            # JJM: Finally, we're able to build up our attribute hash.
-            #    Remember, the hash is keyed by NameService attribute names,
-            #    not DirectoryService attribute names.
-            # NOTE: We're also sort of cheating here...  DirectoryService
-            #   is robust enough to allow multiple values for almost every
-            #   attribute in the system.  Traditional NameService things
-            #   really don't handle this case, so we'll always pull thet first
-            #   value returned from DirectoryService.
-            #   THERE MAY BE AN ORDERING ISSUE HERE, but I think it's ok...
-            attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value_array[0]
+            ds_value = dscl_plist[key][0]  # only care about the first entry...
+            attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value
         end
-        return attribute_hash
+        
+        # NBK: need to read the existing password here as it's not actually
+        # stored in the user record. It is stored at a path that involves the
+        # UUID of the user record for non-Mobile local acccounts.    
+        # Mobile Accounts are out of scope for this provider for now
+        attribute_hash[:password] = self.get_password(attribute_hash[:guid])
+        return attribute_hash    
     end
     
     def self.get_exec_preamble(ds_action, resource_name = nil)
@@ -181,7 +158,7 @@ class DirectoryService < Puppet::Provider::NameService
         #     We EXPECT name to be @resource[:name] when called from an instance object.
 
         # There are two ways to specify paths in 10.5.  See man dscl.
-        command_vector = [ command(:dscl), "-url", "." ]
+        command_vector = [ command(:dscl), "-plist", "." ]
         # JJM: The actual action to perform.  See "man dscl"
         #      Common actiosn: -create, -delete, -merge, -append, -passwd
         command_vector << ds_action
@@ -196,6 +173,52 @@ class DirectoryService < Puppet::Provider::NameService
         #       e.g. 'dscl / -create /Users/mccune'
         return command_vector
     end
+    
+    def self.set_password(resource_name, guid, password_hash)
+        password_hash_file = "#{@@password_hash_dir}/#{guid}"
+        begin
+            File.open(password_hash_file, 'w') { |f| f.write(password_hash)}
+        rescue Errno::EACCES => detail
+            raise Puppet::Error, "Could not write to password hash file: #{detail}"
+        end
+        
+        # NBK: For shadow hashes, the user AuthenticationAuthority must contain a value of
+        # ";ShadowHash;". The LKDC in 10.5 makes this more interesting though as it
+        # will dynamically generate ;Kerberosv5;;username at LKDC:SHA1 attributes if
+        # missing. Thus we make sure we only set ;ShadowHash; if it is missing, and
+        # we can do this with the merge command. This allows people to continue to
+        # use other custom AuthenticationAuthority attributes without stomping on them.
+        #
+        # There is a potential problem here in that we're only doing this when setting
+        # the password, and the attribute could get modified at other times while the 
+        # hash doesn't change and so this doesn't get called at all... but
+        # without switching all the other attributes to merge instead of create I can't
+        # see a simple enough solution for this that doesn't modify the user record
+        # every single time. This should be a rather rare edge case. (famous last words)
+        
+        dscl_vector = self.get_exec_preamble("-merge", resource_name)
+        dscl_vector << "AuthenticationAuthority" << ";ShadowHash;"
+        begin
+            dscl_output = execute(dscl_vector)
+        rescue Puppet::ExecutionFailure => detail
+            raise Puppet::Error, "Could not set AuthenticationAuthority."
+        end
+    end
+    
+    def self.get_password(guid)
+        password_hash = nil
+        password_hash_file = "#{@@password_hash_dir}/#{guid}"
+        # TODO: sort out error conditions?
+        if File.exists?(password_hash_file)
+            if not File.readable?(password_hash_file)
+                raise Puppet::Error("Could not read password hash file at #{password_hash_file} for #{@resource[:name]}")
+            end
+            f = File.new(password_hash_file)
+            password_hash = f.read
+            f.close
+          end
+        password_hash
+    end
 
     def ensure=(ensure_value)
         super
@@ -223,54 +246,19 @@ class DirectoryService < Puppet::Provider::NameService
     end
     
     def password=(passphrase)
-        # JJM: Setting the password is a special case.  We don't just
-        #      set the attribute because we need to update the password
-        #      databases.
-        # FIRST, make sure the AuthenticationAuthority is ;ShadowHash;  If
-        #  we don't do this, we don't get a shadow hash account.  ("Obviously...")
-        dscl_vector = self.class.get_exec_preamble("-create", @resource[:name])
-        dscl_vector << "AuthenticationAuthority" << ";ShadowHash;"
-        begin
-            dscl_output = execute(dscl_vector)
-        rescue Puppet::ExecutionFailure => detail
-            raise Puppet::Error, "Could not set AuthenticationAuthority."
-        end        
-        
-        # JJM: Second, we need to actually set the password.  dscl does
-        #   some magic, creating the proper hash for us based on the
-        #   AuthenticationAuthority attribute, set above.
-        dscl_vector = self.class.get_exec_preamble("-passwd", @resource[:name])
-        dscl_vector << passphrase
-        # JJM: Should we not log the password string?  This may be a security
-        #      risk...
-        begin
-            dscl_output = execute(dscl_vector)
-        rescue Puppet::ExecutionFailure => detail
-            raise Puppet::Error, "Could not set password using command vector: %{dscl_vector.inspect}"
-        end
-    end
-    
-    # JJM: nameservice.rb defines methods for each attribute of the type.
-    #   We implement these methods here, by implementing get() and set()
-    #   See the resource_type= method defined in nameservice.rb
-    #   I'm not sure what the implications are of doing things this way.
-    #   It was a bit difficult to sort out what was happening in my head,
-    #   but ruby-debug makes this process much more transparent.
-    #
-    def set(property, value)
-        # JJM: As it turns out, the set method defined in our parent class
-        #   is fine.  It just calls the modifycmd() method, which
-        #   I'll implement here.
-        super
-    end
-    
-    def get(param)
-        hash = getinfo(false)
-        if hash
-            return hash[param]
-        else
-            return :absent
-        end
+      exec_arg_vector = self.class.get_exec_preamble("-read", @resource.name)
+      exec_arg_vector << @@ns_to_ds_attribute_map[:guid]
+      begin
+          guid_output = execute(exec_arg_vector)
+          guid_plist = Plist.parse_xml(guid_output)
+          # Although GeneratedUID like all DirectoryService values can be multi-valued
+          # according to the schema, in practice user accounts cannot have multiple UUIDs
+          # otherwise Bad Things Happen, so we just deal with the first value.
+          guid = guid_plist["dsAttrTypeStandard:#{@@ns_to_ds_attribute_map[:guid]}"][0]
+          self.class.set_password(@resource.name, guid, passphrase)
+      rescue Puppet::ExecutionFailure => detail
+          raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail]
+      end
     end
     
     def modifycmd(property, value)
@@ -287,15 +275,53 @@ class DirectoryService < Puppet::Provider::NameService
         return exec_arg_vector
     end
     
-    def addcmd
-        # JJM 2007-07-24: 
-        #    - addcmd returns an array to be executed to create a new object.
-        #    - This method is probably being called from the
-        #      ensure= method in nameservice.rb, or here...
-        #    - This should only be called if the object doesn't exist.
-        # JJM: Blame nameservice.rb for the terse method name. =)
-        #
-        self.class.get_exec_preamble("-create", @resource[:name])
+    # NBK: we override @parent.create as we need to execute a series of commands
+    # to create objects with dscl, rather than the single command nameservice.rb
+    # expects to be returned by addcmd. Thus we don't bother defining addcmd.
+    def create
+       if exists?
+            info "already exists"
+            # The object already exists
+            return nil
+        end
+        
+        # NBK: First we create the object with a known guid so we can set the contents
+        # of the password hash if required
+        # Shelling out sucks, but for a single use case it doesn't seem worth
+        # requiring people install a UUID library that doesn't come with the system.
+        # This should be revisited if Puppet starts managing UUIDs for other platform
+        # user records.
+        guid = %x{/usr/bin/uuidgen}.chomp
+        
+        exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
+        exec_arg_vector << @@ns_to_ds_attribute_map[:guid] << guid
+        begin
+          execute(exec_arg_vector)
+        rescue Puppet::ExecutionFailure => detail
+            raise Puppet::Error, "Could not set GeneratedUID for %s %s: %s" %
+                [@resource.class.name, @resource.name, detail]
+        end
+        
+        if value = @resource.should(:password) and value != ""
+          self.class.set_password(@resource[:name], guid, value)
+        end
+        
+        # Now we create all the standard properties
+        Puppet::Type.type(:user).validproperties.each do |property|
+            next if property == :ensure
+            if value = @resource.should(property) and value != ""
+                exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
+                exec_arg_vector << @@ns_to_ds_attribute_map[symbolize(property)]
+                next if property == :password  # skip setting the password here
+                exec_arg_vector << value.to_s
+                begin
+                  execute(exec_arg_vector)
+                rescue Puppet::ExecutionFailure => detail
+                    raise Puppet::Error, "Could not create %s %s: %s" %
+                        [@resource.class.name, @resource.name, detail]
+                end  
+            end
+        end
     end
     
     def deletecmd
@@ -341,6 +367,7 @@ class DirectoryService < Puppet::Provider::NameService
             # list, then report on the remaining list. Pretty whacky, ehh?
             type_properties = [:name] + self.class.resource_type.validproperties
             type_properties.delete(:ensure) if type_properties.include? :ensure
+            type_properties << :guid  # append GeneratedUID so we just get the report here
             @property_value_cache_hash = self.class.single_report(@resource[:name], *type_properties)
         end
         return @property_value_cache_hash

-- 
Puppet packaging for Debian



More information about the Pkg-puppet-devel mailing list