lib/puppet/provider/nameservice/directoryservice.rb in puppet-2.7.26 vs lib/puppet/provider/nameservice/directoryservice.rb in puppet-3.0.0.rc4
- old
+ new
@@ -1,13 +1,11 @@
require 'puppet'
require 'puppet/provider/nameservice'
require 'facter/util/plist'
-require 'cgi'
require 'fileutils'
-class Puppet::Provider::NameService
-class DirectoryService < Puppet::Provider::NameService
+class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameService
# JJM: Dive into the singleton_class
class << self
# JJM: This allows us to pass information when calling
# Puppet::Type.type
# e.g. Puppet::Type.type(:user).provide :directoryservice, :ds_path => "Users"
@@ -30,41 +28,40 @@
# corresponding DirectoryService attribute names.
# See: http://images.apple.com/server/docs.Open_Directory_v10.4.pdf
# JJM: Note, this is de-coupled from the Puppet::Type, and must
# be actively maintained. There may also be collisions with different
# types (Users, Groups, Mounts, Hosts, etc...)
- @@ds_to_ns_attribute_map = {
- 'RecordName' => :name,
- 'PrimaryGroupID' => :gid,
- 'NFSHomeDirectory' => :home,
- 'UserShell' => :shell,
- 'UniqueID' => :uid,
- 'RealName' => :comment,
- 'Password' => :password,
- 'GeneratedUID' => :guid,
- 'IPAddress' => :ip_address,
- 'ENetAddress' => :en_address,
- 'GroupMembership' => :members,
- }
+ def ds_to_ns_attribute_map; self.class.ds_to_ns_attribute_map; end
+ def self.ds_to_ns_attribute_map
+ {
+ 'RecordName' => :name,
+ 'PrimaryGroupID' => :gid,
+ 'NFSHomeDirectory' => :home,
+ 'UserShell' => :shell,
+ 'UniqueID' => :uid,
+ 'RealName' => :comment,
+ 'Password' => :password,
+ 'GeneratedUID' => :guid,
+ 'IPAddress' => :ip_address,
+ 'ENetAddress' => :en_address,
+ 'GroupMembership' => :members,
+ }
+ end
+
# JJM The same table as above, inverted.
- @@ns_to_ds_attribute_map = {
- :name => 'RecordName',
- :gid => 'PrimaryGroupID',
- :home => 'NFSHomeDirectory',
- :shell => 'UserShell',
- :uid => 'UniqueID',
- :comment => 'RealName',
- :password => 'Password',
- :guid => 'GeneratedUID',
- :en_address => 'ENetAddress',
- :ip_address => 'IPAddress',
- :members => 'GroupMembership',
- }
+ def ns_to_ds_attribute_map; self.class.ns_to_ds_attribute_map end
+ def self.ns_to_ds_attribute_map
+ @ns_to_ds_attribute_map ||= ds_to_ns_attribute_map.invert
+ end
- @@password_hash_dir = "/var/db/shadow/hash"
- @@users_plist_dir = '/var/db/dslocal/nodes/Default/users'
+ def self.password_hash_dir
+ '/var/db/shadow/hash'
+ end
+ def self.users_plist_dir
+ '/var/db/dslocal/nodes/Default/users'
+ end
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.
@@ -94,20 +91,13 @@
return @macosx_version_major if defined?(@macosx_version_major)
begin
# Make sure we've loaded all of the facts
Facter.loadfacts
- if Facter.value(:macosx_productversion_major)
- product_version_major = Facter.value(:macosx_productversion_major)
- else
- # TODO: remove this code chunk once we require Facter 1.5.5 or higher.
- Puppet.warning("DEPRECATION WARNING: Future versions of the directoryservice provider will require Facter 1.5.5 or newer.")
- product_version = Facter.value(:macosx_productversion)
- fail("Could not determine OS X version from Facter") if product_version.nil?
- product_version_major = product_version.scan(/(\d+)\.(\d+)./).join(".")
- end
- fail("#{product_version_major} is not supported by the directoryservice provider") if %w{10.0 10.1 10.2 10.3}.include?(product_version_major)
+ product_version_major = Facter.value(:macosx_productversion_major)
+
+ fail("#{product_version_major} is not supported by the directoryservice provider") if %w{10.0 10.1 10.2 10.3 10.4}.include?(product_version_major)
@macosx_version_major = product_version_major
return @macosx_version_major
rescue Puppet::ExecutionFailure => detail
fail("Could not determine OS X version: #{detail}")
end
@@ -122,61 +112,21 @@
fail("Could not get #{@resource_type.name} list from DirectoryService")
end
dscl_output.split("\n")
end
- def self.parse_dscl_url_data(dscl_output)
- # we need to construct a Hash from the dscl -url output to match
- # that returned by the dscl -plist output for 10.5+ clients.
- #
- # Nasty assumptions:
- # a) no values *end* in a colon ':', only keys
- # b) if a line ends in a colon and the next line does start with
- # a space, then the second line is a value of the first.
- # c) (implied by (b)) keys don't start with spaces.
-
- dscl_plist = {}
- dscl_output.split("\n").inject([]) do |array, line|
- if line =~ /^\s+/ # it's a value
- array[-1] << line # add the value to the previous key
- else
- array << line
- end
- array
- end.compact
-
- dscl_output.each do |line|
- # This should be a 'normal' entry. key and value on one line.
- # We split on ': ' to deal with keys/values with a colon in them.
- split_array = line.split(/:\s+/)
- key = split_array.first
- value = CGI::unescape(split_array.last.strip.chomp)
- # We need to treat GroupMembership separately as it is currently
- # the only attribute we care about multiple values for, and
- # the values can never contain spaces (shortnames)
- # We also make every value an array to be consistent with the
- # output of dscl -plist under 10.5
- if key == "GroupMembership"
- dscl_plist[key] = value.split(/\s/)
- else
- dscl_plist[key] = [value]
- end
- end
- dscl_plist
- end
-
def self.parse_dscl_plist_data(dscl_output)
Plist.parse_xml(dscl_output)
end
def self.generate_attribute_hash(input_hash, *type_properties)
attribute_hash = {}
input_hash.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])
+ next unless (ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? ds_to_ns_attribute_map[ds_attribute])
ds_value = input_hash[key]
- case @@ds_to_ns_attribute_map[ds_attribute]
+ case ds_to_ns_attribute_map[ds_attribute]
when :members
ds_value = ds_value # only members uses arrays so far
when :gid, :uid
# OS X stores objects like uid/gid as strings.
# Try casting to an integer for these cases to be
@@ -187,11 +137,11 @@
rescue ArgumentError
ds_value = ds_value[0]
end
else ds_value = ds_value[0]
end
- attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value
+ attribute_hash[ds_to_ns_attribute_map[ds_attribute]] = ds_value
end
# 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.
@@ -218,44 +168,32 @@
dscl_output = execute(dscl_vector)
rescue Puppet::ExecutionFailure => detail
fail("Could not get report. command execution failed.")
end
- # Two code paths is ugly, but until we can drop 10.4 support we don't
- # have a lot of choice. Ultimately this should all be done using Ruby
- # to access the DirectoryService APIs directly, but that's simply not
- # feasible for a while yet.
- if self.get_macosx_version_major > "10.4"
- dscl_plist = self.parse_dscl_plist_data(dscl_output)
- elsif self.get_macosx_version_major == "10.4"
- dscl_plist = self.parse_dscl_url_data(dscl_output)
- else
- fail("Puppet does not support OS X versions < 10.4")
- end
+ # (#11593) Remove support for OS X 10.4 and earlier
+ fail_if_wrong_version
+ dscl_plist = self.parse_dscl_plist_data(dscl_output)
self.generate_attribute_hash(dscl_plist, *type_properties)
end
+ def self.fail_if_wrong_version
+ fail("Puppet does not support OS X versions < 10.5") unless self.get_macosx_version_major >= "10.5"
+ end
+
def self.get_exec_preamble(ds_action, resource_name = nil)
# JJM 2007-07-24
# DSCL commands are often repetitive and contain the same positional
# arguments over and over. See http://developer.apple.com/documentation/Porting/Conceptual/PortingUnix/additionalfeatures/chapter_10_section_9.html
# for an example of what I mean.
# This method spits out proper DSCL commands for us.
# We EXPECT name to be @resource[:name] when called from an instance object.
- # 10.4 doesn't support the -plist option for dscl, and 10.5 has a
- # different format for the -url output with objects with spaces in
- # their values. *sigh*. Use -url for 10.4 in the hope this can be
- # deprecated one day, and use -plist for 10.5 and higher.
- if self.get_macosx_version_major > "10.4"
- command_vector = [ command(:dscl), "-plist", "." ]
- elsif self.get_macosx_version_major == "10.4"
- command_vector = [ command(:dscl), "-url", "." ]
- else
- fail("Puppet does not support OS X versions < 10.4")
- end
+ # (#11593) Remove support for OS X 10.4 and earlier
+ fail_if_wrong_version
+ 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
# JJM: get_ds_path will spit back "Users" or "Groups",
@@ -273,11 +211,11 @@
def self.set_password(resource_name, guid, password_hash)
# Use Puppet::Util::Package.versioncmp() to catch the scenario where a
# version '10.10' would be < '10.7' with simple string comparison. This
# if-statement only executes if the current version is less-than 10.7
if (Puppet::Util::Package.versioncmp(get_macosx_version_major, '10.7') == -1)
- password_hash_file = "#{@@password_hash_dir}/#{guid}"
+ password_hash_file = "#{password_hash_dir}/#{guid}"
begin
File.open(password_hash_file, 'w') { |f| f.write(password_hash)}
rescue Errno::EACCES => detail
fail("Could not write to password hash file: #{detail}")
end
@@ -311,17 +249,17 @@
if password_hash.length != 136
fail("OS X 10.7 requires a Salted SHA512 hash password of 136 characters. \
Please check your password and try again.")
end
- if File.exists?("#{@@users_plist_dir}/#{resource_name}.plist")
+ if File.exists?("#{users_plist_dir}/#{resource_name}.plist")
# If a plist already exists in /var/db/dslocal/nodes/Default/users, then
# we will need to extract the binary plist from the 'ShadowHashData'
# key, log the new password into the resultant plist's 'SALTED-SHA512'
# key, and then save the entire structure back.
users_plist = Plist::parse_xml(plutil( '-convert', 'xml1', '-o', '/dev/stdout', \
- "#{@@users_plist_dir}/#{resource_name}.plist"))
+ "#{users_plist_dir}/#{resource_name}.plist"))
# users_plist['ShadowHashData'][0].string is actually a binary plist
# that's nested INSIDE the user's plist (which itself is a binary
# plist). If we encounter a user plist that DOESN'T have a
# ShadowHashData field, create one.
@@ -343,36 +281,36 @@
# Finally, we can convert the nested plist back to binary, embed it
# into the user's plist, and convert the resultant plist back to
# a binary plist.
changed_plist = convert_xml_to_binary(converted_hash_plist)
users_plist['ShadowHashData'][0].string = changed_plist
- Plist::Emit.save_plist(users_plist, "#{@@users_plist_dir}/#{resource_name}.plist")
- plutil('-convert', 'binary1', "#{@@users_plist_dir}/#{resource_name}.plist")
+ Plist::Emit.save_plist(users_plist, "#{users_plist_dir}/#{resource_name}.plist")
+ plutil('-convert', 'binary1', "#{users_plist_dir}/#{resource_name}.plist")
end
end
end
def self.get_password(guid, username)
# Use Puppet::Util::Package.versioncmp() to catch the scenario where a
# version '10.10' would be < '10.7' with simple string comparison. This
# if-statement only executes if the current version is less-than 10.7
if (Puppet::Util::Package.versioncmp(get_macosx_version_major, '10.7') == -1)
password_hash = nil
- password_hash_file = "#{@@password_hash_dir}/#{guid}"
+ password_hash_file = "#{password_hash_dir}/#{guid}"
if File.exists?(password_hash_file) and File.file?(password_hash_file)
fail("Could not read password hash file at #{password_hash_file}") if not File.readable?(password_hash_file)
f = File.new(password_hash_file)
password_hash = f.read
f.close
end
password_hash
else
- if File.exists?("#{@@users_plist_dir}/#{username}.plist")
+ if File.exists?("#{users_plist_dir}/#{username}.plist")
# If a plist exists in /var/db/dslocal/nodes/Default/users, we will
# extract the binary plist from the 'ShadowHashData' key, decode the
# salted-SHA512 password hash, and then return it.
- users_plist = Plist::parse_xml(plutil('-convert', 'xml1', '-o', '/dev/stdout', "#{@@users_plist_dir}/#{username}.plist"))
+ users_plist = Plist::parse_xml(plutil('-convert', 'xml1', '-o', '/dev/stdout', "#{users_plist_dir}/#{username}.plist"))
if users_plist['ShadowHashData']
# users_plist['ShadowHashData'][0].string is actually a binary plist
# that's nested INSIDE the user's plist (which itself is a binary
# plist).
password_hash_plist = users_plist['ShadowHashData'][0].string
@@ -465,18 +403,18 @@
end
end
def password=(passphrase)
exec_arg_vector = self.class.get_exec_preamble("-read", @resource.name)
- exec_arg_vector << @@ns_to_ds_attribute_map[:guid]
+ 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]
+ guid = guid_plist["dsAttrTypeStandard:#{ns_to_ds_attribute_map[:guid]}"][0]
self.class.set_password(@resource.name, guid, passphrase)
rescue Puppet::ExecutionFailure => detail
fail("Could not set #{param} on #{@resource.class.name}[#{@resource.name}]: #{detail}")
end
end
@@ -498,11 +436,11 @@
add_members(current_members, value)
else
exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
# JJM: The following line just maps the NS name to the DS name
# e.g. { :uid => 'UniqueID' }
- exec_arg_vector << @@ns_to_ds_attribute_map[param.intern]
+ exec_arg_vector << ns_to_ds_attribute_map[param.intern]
# JJM: The following line sends the actual value to set the property to
exec_arg_vector << value.to_s
begin
execute(exec_arg_vector)
rescue Puppet::ExecutionFailure => detail
@@ -527,11 +465,11 @@
# 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
+ exec_arg_vector << ns_to_ds_attribute_map[:guid] << guid
begin
execute(exec_arg_vector)
rescue Puppet::ExecutionFailure => detail
fail("Could not set GeneratedUID for #{@resource.class.name} #{@resource.name}: #{detail}")
end
@@ -553,11 +491,11 @@
if value != "" and not value.nil?
if property == :members
add_members(nil, value)
else
exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name])
- exec_arg_vector << @@ns_to_ds_attribute_map[property.intern]
+ exec_arg_vector << ns_to_ds_attribute_map[property.intern]
next if property == :password # skip setting the password here
exec_arg_vector << value.to_s
begin
execute(exec_arg_vector)
rescue Puppet::ExecutionFailure => detail
@@ -645,8 +583,7 @@
@property_value_cache_hash[param] = @property_value_cache_hash[param].to_i if @property_value_cache_hash and @property_value_cache_hash.include?(param)
end
end
@property_value_cache_hash
end
-end
end