lib/chef/provider/user/dscl.rb in chef-0.8.16 vs lib/chef/provider/user/dscl.rb in chef-0.9.0.a3

- old
+ new

@@ -14,34 +14,33 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # +require 'chef/mixin/shell_out' require 'chef/provider/user' require 'openssl' class Chef class Provider class User class Dscl < Chef::Provider::User + include Chef::Mixin::ShellOut + NFS_HOME_DIRECTORY = %r{^NFSHomeDirectory: (.*)$} + AUTHENTICATION_AUTHORITY = %r{^AuthenticationAuthority: (.*)$} + def dscl(*args) - host = "." - stdout_result = ""; stderr_result = ""; cmd = "dscl #{host} -#{args.join(' ')}" - status = popen4(cmd) do |pid, stdin, stdout, stderr| - stdout.each { |line| stdout_result << line } - stderr.each { |line| stderr_result << line } - end - return [cmd, status, stdout_result, stderr_result] + shell_out("dscl . -#{args.join(' ')}") end def safe_dscl(*args) result = dscl(*args) - return "" if ( args.first =~ /^delete/ ) && ( result[1].exitstatus != 0 ) - raise(Chef::Exceptions::User,"dscl error: #{result.inspect}") unless result[1].exitstatus == 0 - raise(Chef::Exceptions::User,"dscl error: #{result.inspect}") if result[2] =~ /No such key: / - return result[2] + return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 ) + raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") unless result.exitstatus == 0 + raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") if result.stdout =~ /No such key: / + return result.stdout end # This is handled in providers/group.rb by Etc.getgrnam() # def user_exists?(user) # users = safe_dscl("list /Users") @@ -68,95 +67,68 @@ users_uids = safe_dscl("list /Users uid") !! ( users_uids =~ Regexp.new("#{uid}\n") ) end def set_uid - @new_resource.uid(get_free_uid) if [nil,""].include? @new_resource.uid - raise(Chef::Exceptions::User,"uid is already in use") if uid_used?(@new_resource.uid) + @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '') + if uid_used?(@new_resource.uid) + raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use") + end safe_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}") end def modify_home return safe_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory") if (@new_resource.home.nil? || @new_resource.home.empty?) if @new_resource.supports[:manage_home] - unless @new_resource.home =~ /^\// - raise(Chef::Exceptions::User,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'") - end - - ch_eq_nh = ( @current_resource.home == @new_resource.home ) - cur_home_exists = ::File.exists?("#{@current_resource.home}") - new_home_exists = ::File.exists?("#{@new_resource.home}") - ditto = false - move = false + validate_home_dir_specification! - if ch_eq_nh - if !new_home_exists - ditto = true - end - else - if !cur_home_exists - if !new_home_exists - ditto = true - end - elsif cur_home_exists - move = true - end + if (@current_resource.home == @new_resource.home) && !new_home_exists? + ditto_home + elsif !current_home_exists? && !new_home_exists? + ditto_home + elsif current_home_exists? + move_home end - - if ditto - skel = "/System/Library/User Template/English.lproj" - raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel) - run_command(:command => "ditto '#{skel}' '#{@new_resource.home}'") - ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home) - end - - if move - src = @current_resource.home - FileUtils.mkdir_p(@new_resource.home) - files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."] - ::FileUtils.mv(files,@new_resource.home, :force => true) - ::FileUtils.rmdir(src) - ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home) - end end safe_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'") - end + end def osx_shadow_hash?(string) return !! ( string =~ /^[[:xdigit:]]{1240}$/ ) end def osx_salted_sha1?(string) return !! ( string =~ /^[[:xdigit:]]{48}$/ ) end def guid - safe_dscl("read /Users/#{@new_resource.username} GeneratedUID").gsub(/GeneratedUID: /,"").gsub!(/\n/,"") + safe_dscl("read /Users/#{@new_resource.username} GeneratedUID").gsub(/GeneratedUID: /,"").strip end def shadow_hash_set? - if safe_dscl("read /Users/#{@new_resource.username}") =~ /AuthenticationAuthority: / - auth_auth = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority") - return !! ( auth_auth =~ /ShadowHash/ ) + user_data = safe_dscl("read /Users/#{@new_resource.username}") + if user_data =~ /AuthenticationAuthority: / && user_data =~ /ShadowHash/ + true + else + false end - return false end def modify_password if @new_resource.password shadow_hash = nil Chef::Log.debug("#{new_resource}: updating password") if osx_shadow_hash?(@new_resource.password) shadow_hash = @new_resource.password.upcase else - salted_sha1 = nil if osx_salted_sha1?(@new_resource.password) salted_sha1 = @new_resource.password.upcase else - hex_salt = ""; chars = ("0".."9").to_a + ("a".."f").to_a - 1.upto(8) { |i| hex_salt << chars[::Kernel.rand(chars.size-1)] } + hex_salt = "" + OpenSSL::Random.random_bytes(10).each_byte { |b| hex_salt << b.to_i.to_s(16) } + hex_salt = hex_salt.slice(0...8) salt = [hex_salt].pack("H*") sha1 = ::OpenSSL::Digest::SHA1.hexdigest(salt+@new_resource.password) salted_sha1 = (hex_salt+sha1).upcase end shadow_hash = String.new("00000000"*155) @@ -177,66 +149,56 @@ super raise Chef::Exceptions::User, "Could not find binary /usr/bin/dscl for #{@new_resource}" unless ::File.exists?("/usr/bin/dscl") end def create_user - manage_user(false) + dscl_create_user + dscl_create_comment + set_uid + dscl_set_gid + modify_home + dscl_set_shell + modify_password end - def manage_user(manage = true) - fields = [] - if manage - [:username,:comment,:uid,:gid,:home,:shell,:password].each do |field| - if @current_resource.send(field) != @new_resource.send(field) - fields << field if @new_resource.send(field) - end - end - if @new_resource.send(:supports)[:manage_home] - fields << :home if @new_resource.send(:home) - end - fields << :shell if fields.include?(:password) + def manage_user + dscl_create_user if diverged?(:username) + dscl_create_comment if diverged?(:comment) + set_uid if diverged?(:uid) + dscl_set_gid if diverged?(:uid) + modify_home if diverged?(:home) + dscl_set_shell if diverged?(:shell) + modify_password if diverged?(:password) + end + + def dscl_create_user + safe_dscl("create /Users/#{@new_resource.username}") + end + + def dscl_create_comment + safe_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'") + end + + def dscl_set_gid + safe_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'") + end + + def dscl_set_shell + if @new_resource.password || ::File.exists?("#{@new_resource.shell}") + safe_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'") else - # create - fields = [:username,:comment,:uid,:gid,:home,:shell,:password] + safe_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'") end - fields.uniq! - fields.each do |field| - case field - when :username - safe_dscl("create /Users/#{@new_resource.username}") - - when :comment - safe_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'") - - when :uid - set_uid - - when :gid - safe_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'") - - when :home - modify_home - - when :shell - if @new_resource.password || ::File.exists?("#{@new_resource.shell}") - safe_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'") - else - safe_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'") - end - - when :password - modify_password - end - end end def remove_user if @new_resource.supports[:manage_home] - # remove home directory - if safe_dscl("read /Users/#{@new_resource.username}") =~ /NFSHomeDirectory/ - nfs_home = safe_dscl("read /Users/#{@new_resource.username} NFSHomeDirectory") - nfs_home.gsub!(/NFSHomeDirectory: /,"").gsub!(/\n$/,"") + user_info = safe_dscl("read /Users/#{@new_resource.username}") + if nfs_home_match = user_info.match(NFS_HOME_DIRECTORY) + #nfs_home = safe_dscl("read /Users/#{@new_resource.username} NFSHomeDirectory") + #nfs_home.gsub!(/NFSHomeDirectory: /,"").gsub!(/\n$/,"") + nfs_home = nfs_home_match[1] FileUtils.rm_rf(nfs_home) end end # remove the user from its groups groups = [] @@ -249,15 +211,16 @@ # remove user account safe_dscl("delete /Users/#{@new_resource.username}") end def locked? - if safe_dscl("read /Users/#{@new_resource.username}") =~ /AuthenticationAuthority: / - auth_auth = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority") - return !! ( auth_auth =~ /DisabledUser/ ) + user_info = safe_dscl("read /Users/#{@new_resource.username}") + if auth_authority_md = AUTHENTICATION_AUTHORITY.match(user_info) + !!(auth_authority_md[1] =~ /DisabledUser/ ) + else + false end - return false end def check_lock return @locked = locked? end @@ -265,12 +228,52 @@ def lock_user safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'") end def unlock_user - auth_auth = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority") - auth_auth.gsub!(/AuthenticationAuthority: /,"").gsub!(/DisabledUser/,"").gsub!(/[; ]*$/,"") - safe_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_auth}'") + auth_info = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority") + auth_string = auth_info.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip#.gsub!(/[; ]*$/,"") + safe_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'") + end + + def validate_home_dir_specification! + unless @new_resource.home =~ /^\// + raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'") + end + end + + def current_home_exists? + ::File.exist?("#{@current_resource.home}") + end + + def new_home_exists? + ::File.exist?("#{@new_resource.home}") + end + + def ditto_home + skel = "/System/Library/User Template/English.lproj" + raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel) + shell_out! "ditto '#{skel}' '#{@new_resource.home}'" + ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home) + end + + def move_home + Chef::Log.debug("moving #{self} home from #{@current_resource.home} to #{@new_resource.home}") + + src = @current_resource.home + FileUtils.mkdir_p(@new_resource.home) + files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."] + ::FileUtils.mv(files,@new_resource.home, :force => true) + ::FileUtils.rmdir(src) + ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home) + end + + def diverged?(parameter) + parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?) + end + + def parameter_updated?(parameter) + not (@new_resource.send(parameter) == @current_resource.send(parameter)) end end end end end