summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith84@gmail.com>2020-11-24 12:28:38 -0800
committerTim Smith <tsmith84@gmail.com>2020-11-24 12:45:07 -0800
commitf2523cb55f63c94dfd393adb490863e48a383e0b (patch)
tree0cd5534f397fe80ddffb17a3a6f6ebcf968ebc56
parent3d7728a6ae3f7baed8c3d6bd4f1612607f6bce74 (diff)
downloadchef-remove_dscl.tar.gz
Fully remove user resource support for macOS < 10.14remove_dscl
This builds on the removal of macOS 10.13 from the build matrix in https://github.com/chef/chef/pull/10680. With 10.14+ required for Chef packages we can safely remove the dscl_user resource and just assume 10.14+ when creating users. Signed-off-by: Tim Smith <tsmith@chef.io>
-rw-r--r--lib/chef/provider/user/dscl.rb637
-rw-r--r--lib/chef/provider/user/mac.rb5
-rw-r--r--lib/chef/providers.rb1
-rw-r--r--lib/chef/resource/user/dscl_user.rb35
-rw-r--r--lib/chef/resource/user/mac_user.rb2
-rw-r--r--lib/chef/resources.rb1
-rw-r--r--spec/functional/resource/user/dscl_spec.rb188
-rw-r--r--spec/functional/resource/user/mac_user_spec.rb4
-rw-r--r--spec/spec_helper.rb2
-rw-r--r--spec/support/platform_helpers.rb8
-rw-r--r--spec/unit/provider/user/dscl_spec.rb699
-rwxr-xr-xtasks/docs.rb2
12 files changed, 6 insertions, 1578 deletions
diff --git a/lib/chef/provider/user/dscl.rb b/lib/chef/provider/user/dscl.rb
deleted file mode 100644
index 7b266b8d62..0000000000
--- a/lib/chef/provider/user/dscl.rb
+++ /dev/null
@@ -1,637 +0,0 @@
-#
-# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
-# Copyright:: Copyright (c) Chef Software Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# 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_relative "../../mixin/shell_out"
-require_relative "../user"
-require_relative "../../resource/user/dscl_user"
-autoload :OpenSSL, "openssl"
-autoload :Plist, "plist"
-require_relative "../../util/path_helper"
-
-class Chef
- class Provider
- class User
- #
- # The most tricky bit of this provider is the way it deals with user passwords.
- # macOS has different password shadow calculations based on the version.
- # < 10.7 => password shadow calculation format SALTED-SHA1
- # => stored in: /var/db/shadow/hash/#{guid}
- # => shadow binary length 68 bytes
- # => First 4 bytes salt / Next 64 bytes shadow value
- # = 10.7 => password shadow calculation format SALTED-SHA512
- # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist
- # => shadow binary length 68 bytes
- # => First 4 bytes salt / Next 64 bytes shadow value
- # > 10.7 => password shadow calculation format SALTED-SHA512-PBKDF2
- # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist
- # => shadow binary length 128 bytes
- # => Salt / Iterations are stored separately in the same file
- #
- # This provider only supports macOS versions 10.7 to 10.13
- class Dscl < Chef::Provider::User
-
- attr_accessor :user_info
- attr_accessor :authentication_authority
- attr_accessor :password_shadow_conversion_algorithm
-
- provides :dscl_user
- provides :user, os: "darwin", platform_version: "<= 10.13"
-
- # Just-in-case a recipe calls the user dscl provider without specifying
- # a gid property. Avoids chown issues in move_home when the manage_home
- # property is in use. #5393
- STAFF_GROUP_ID = 20
-
- def define_resource_requirements
- super
-
- requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exist?("/usr/bin/dscl") }
- a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{new_resource}!")
- end
-
- requirements.assert(:all_actions) do |a|
- a.assertion { ::File.exist?("/usr/bin/plutil") }
- a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{new_resource}!")
- end
-
- requirements.assert(:create, :modify, :manage) do |a|
- a.assertion do
- if new_resource.password
- # SALTED-SHA512 password shadow hashes are not supported on 10.8 and above.
- !salted_sha512?(new_resource.password)
- else
- true
- end
- end
- a.failure_message(Chef::Exceptions::User, "SALTED-SHA512 passwords are not supported on Mac 10.8 and above. \
-If you want to set the user password using shadow info make sure you specify a SALTED-SHA512-PBKDF2 shadow hash \
-in 'password', with the associated 'salt' and 'iterations'.")
- end
-
- requirements.assert(:create, :modify, :manage) do |a|
- a.assertion do
- if new_resource.password && salted_sha512_pbkdf2?(new_resource.password)
- # salt and iterations should be specified when
- # SALTED-SHA512-PBKDF2 password shadow hash is given
- !new_resource.salt.nil? && !new_resource.iterations.nil?
- else
- true
- end
- end
- a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hash is given without associated \
-'salt' and 'iterations'. Please specify 'salt' and 'iterations' in order to set the user password using shadow hash.")
- end
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::User::DsclUser.new(new_resource.username)
- current_resource.username(new_resource.username)
-
- @user_info = read_user_info
- if user_info
- current_resource.uid(dscl_get(user_info, :uid))
- current_resource.gid(dscl_get(user_info, :gid))
- current_resource.home(dscl_get(user_info, :home))
- current_resource.shell(dscl_get(user_info, :shell))
- current_resource.comment(dscl_get(user_info, :comment))
- @authentication_authority = dscl_get(user_info, :auth_authority)
-
- if new_resource.password && dscl_get(user_info, :password) == "********"
- # A password is set. Let's get the password information from shadow file
- shadow_hash_binary = dscl_get(user_info, :shadow_hash)
-
- # Calling shell_out directly since we want to give an input stream
- shadow_hash_xml = convert_binary_plist_to_xml(shadow_hash_binary.string)
- shadow_hash = ::Plist.parse_xml(shadow_hash_xml)
-
- if shadow_hash["SALTED-SHA512-PBKDF2"] # 10.7+ contains this, but we retain the check in case it goes away in the future
- @password_shadow_conversion_algorithm = "SALTED-SHA512-PBKDF2"
- # Convert the entropy from Base64 encoding to hex before consuming them
- current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first)
- current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"])
- # Convert the salt from Base64 encoding to hex before consuming them
- current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack("H*").first)
- else
- raise(Chef::Exceptions::User, "Unknown shadow_hash format: #{shadow_hash.keys.join(" ")}")
- end
- end
-
- convert_group_name if new_resource.gid
- else
- @user_exists = false
- logger.trace("#{new_resource} user does not exist")
- end
-
- current_resource
- end
-
- #
- # Provider Actions
- #
-
- def create_user
- dscl_create_user
- # set_password modifies the plist file of the user directly. So update
- # the password first before making any modifications to the user.
- set_password
- dscl_create_comment
- dscl_set_uid
- dscl_set_gid
- dscl_set_home
- dscl_set_shell
- end
-
- def manage_user
- # set_password modifies the plist file of the user directly. So update
- # the password first before making any modifications to the user.
- set_password if diverged_password?
- dscl_create_user if diverged?(:username)
- dscl_create_comment if diverged?(:comment)
- dscl_set_uid if diverged?(:uid)
- dscl_set_gid if diverged?(:gid)
- dscl_set_home if diverged?(:home)
- dscl_set_shell if diverged?(:shell)
- end
-
- #
- # Action Helpers
- #
-
- #
- # Create a user using dscl
- #
- def dscl_create_user
- run_dscl("create", "/Users/#{new_resource.username}")
- end
-
- #
- # Saves the specified Chef user `comment` into RealName attribute
- # of Mac user. If `comment` is not specified, it takes `username` value.
- #
- def dscl_create_comment
- comment = new_resource.comment || new_resource.username
- run_dscl("create", "/Users/#{new_resource.username}", "RealName", comment)
- end
-
- #
- # Sets the user id for the user using dscl.
- # If a `uid` is not specified, it finds the next available one starting
- # from 200 if `system` is set, 501 otherwise.
- #
- def dscl_set_uid
- # XXX: mutates the new resource
- 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
-
- run_dscl("create", "/Users/#{new_resource.username}", "UniqueID", new_resource.uid)
- end
-
- #
- # Find the next available uid on the system. starting with 200 if `system` is set,
- # 501 otherwise.
- #
- def get_free_uid(search_limit = 1000)
- uid = nil
- base_uid = new_resource.system ? 200 : 501
- next_uid_guess = base_uid
- users_uids = run_dscl("list", "/Users", "uid")
- while next_uid_guess < search_limit + base_uid
- if users_uids&.match?(Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n"))
- next_uid_guess += 1
- else
- uid = next_uid_guess
- break
- end
- end
- uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
- end
-
- #
- # Returns true if uid is in use by a different account, false otherwise.
- #
- def uid_used?(uid)
- return false unless uid
-
- users_uids = run_dscl("list", "/Users", "uid").split("\n")
- uid_map = users_uids.each_with_object({}) do |tuid, tmap|
- x = tuid.split
- tmap[x[1]] = x[0]
- tmap
- end
- if uid_map[uid.to_s]
- unless uid_map[uid.to_s] == new_resource.username
- return true
- end
- end
- false
- end
-
- #
- # Sets the group id for the user using dscl. Fails if a group doesn't
- # exist on the system with given group id. If `gid` is not specified, it
- # sets a default Mac user group "staff", with id 20 using the CONSTANT
- #
- def dscl_set_gid
- if new_resource.gid.nil?
- # XXX: mutates the new resource
- new_resource.gid(STAFF_GROUP_ID)
- elsif !new_resource.gid.to_s.match(/^\d+$/)
- begin
- possible_gid = run_dscl("read", "/Groups/#{new_resource.gid}", "PrimaryGroupID").split(" ").last
- rescue Chef::Exceptions::DsclCommandFailed
- raise Chef::Exceptions::GroupIDNotFound, "Group not found for #{new_resource.gid} when creating user #{new_resource.username}"
- end
- # XXX: mutates the new resource
- new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
- end
- run_dscl("create", "/Users/#{new_resource.username}", "PrimaryGroupID", new_resource.gid)
- end
-
- #
- # Sets the home directory for the user. If `:manage_home` is set home
- # directory is managed (moved / created) for the user.
- #
- def dscl_set_home
- if new_resource.home.nil? || new_resource.home.empty?
- run_dscl("delete", "/Users/#{new_resource.username}", "NFSHomeDirectory")
- return
- end
-
- if new_resource.manage_home
- validate_home_dir_specification!
-
- 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
- end
- run_dscl("create", "/Users/#{new_resource.username}", "NFSHomeDirectory", new_resource.home)
- end
-
- def validate_home_dir_specification!
- unless %r{^/}.match?(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?
- !!current_resource.home && ::File.exist?(current_resource.home)
- end
-
- def new_home_exists?
- ::File.exist?(new_resource.home)
- end
-
- def ditto_home
- shell_out!("/usr/sbin/createhomedir", "-c", "-u", (new_resource.username).to_s)
- end
-
- def move_home
- logger.trace("#{new_resource} moving #{self} home from #{current_resource.home} to #{new_resource.home}")
- new_resource.gid(STAFF_GROUP_ID) if new_resource.gid.nil?
- src = current_resource.home
- FileUtils.mkdir_p(new_resource.home)
- files = ::Dir.glob("#{Chef::Util::PathHelper.escape_glob_dir(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
-
- #
- # Sets the shell for the user using dscl.
- #
- def dscl_set_shell
- if new_resource.shell
- run_dscl("create", "/Users/#{new_resource.username}", "UserShell", new_resource.shell)
- else
- run_dscl("create", "/Users/#{new_resource.username}", "UserShell", "/usr/bin/false")
- end
- end
-
- #
- # Sets the password for the user based on given password parameters.
- # Chef supports specifying plain-text passwords and password shadow
- # hash data.
- #
- def set_password
- # Return if there is no password to set
- return if new_resource.password.nil?
-
- shadow_info = prepare_password_shadow_info
-
- # Shadow info is saved as binary plist. Convert the info to binary plist.
- shadow_info_binary = StringIO.new
- shell_out("plutil", "-convert", "binary1", "-o", "-", "-",
- input: shadow_info.to_plist, live_stream: shadow_info_binary)
-
- if user_info.nil?
- # User is just created. read_user_info() will read the fresh information
- # for the user with a cache flush. However with experimentation we've seen
- # that dscl cache is not immediately updated after the creation of the user
- # This is odd and needs to be investigated further.
- sleep 3
- @user_info = read_user_info
- end
-
- # Replace the shadow info in user's plist
- dscl_set(user_info, :shadow_hash, shadow_info_binary)
- save_user_info(user_info)
- end
-
- #
- # Prepares the password shadow info based on the platform version.
- #
- def prepare_password_shadow_info
- shadow_info = {}
- entropy = nil
- salt = nil
- iterations = nil
-
- if salted_sha512_pbkdf2?(new_resource.password)
- entropy = convert_to_binary(new_resource.password)
- salt = convert_to_binary(new_resource.salt)
- iterations = new_resource.iterations
- else
- salt = OpenSSL::Random.random_bytes(32)
- iterations = new_resource.iterations # Use the default if not specified by the user
-
- entropy = OpenSSL::PKCS5.pbkdf2_hmac(
- new_resource.password,
- salt,
- iterations,
- 128,
- OpenSSL::Digest.new("SHA512")
- )
- end
-
- pbkdf_info = {}
- pbkdf_info["entropy"] = StringIO.new
- pbkdf_info["entropy"].string = entropy
- pbkdf_info["salt"] = StringIO.new
- pbkdf_info["salt"].string = salt
- pbkdf_info["iterations"] = iterations
-
- shadow_info["SALTED-SHA512-PBKDF2"] = pbkdf_info
- shadow_info
- end
-
- #
- # Removes the user from the system after removing user from his groups
- # and deleting home directory if needed.
- #
- def remove_user
- if new_resource.manage_home
- # Remove home directory
- FileUtils.rm_rf(current_resource.home)
- end
-
- # Remove the user from its groups
- run_dscl("list", "/Groups").each_line do |group|
- if member_of_group?(group.chomp)
- run_dscl("delete", "/Groups/#{group.chomp}", "GroupMembership", new_resource.username)
- end
- end
-
- # Remove user account
- run_dscl("delete", "/Users/#{new_resource.username}")
- end
-
- #
- # Locks the user.
- #
- def lock_user
- run_dscl("append", "/Users/#{new_resource.username}", "AuthenticationAuthority", ";DisabledUser;")
- end
-
- #
- # Unlocks the user
- #
- def unlock_user
- auth_string = authentication_authority.gsub(/AuthenticationAuthority: /, "").gsub(/;DisabledUser;/, "").strip
- run_dscl("create", "/Users/#{new_resource.username}", "AuthenticationAuthority", auth_string)
- end
-
- #
- # Returns true if the user is locked, false otherwise.
- #
- def locked?
- if authentication_authority
- !!(authentication_authority =~ /DisabledUser/ )
- else
- false
- end
- end
-
- #
- # This is the interface base User provider requires to provide idempotency.
- #
- def check_lock
- @locked = locked?
- end
-
- #
- # Helper functions
- #
-
- #
- # Returns true if the system state and desired state is different for
- # given attribute.
- #
- def diverged?(parameter)
- parameter_updated?(parameter) && !new_resource.send(parameter).nil?
- end
-
- def parameter_updated?(parameter)
- !(new_resource.send(parameter) == current_resource.send(parameter))
- end
-
- #
- # We need a special check function for password since we support both
- # plain text and shadow hash data.
- #
- # Checks if password needs update based on platform version and the
- # type of the password specified.
- #
- def diverged_password?
- return false if new_resource.password.nil?
-
- # Dscl provider supports both plain text passwords and shadow hashes.
- #
- # Some system users don't have salts; this can happen if the system is
- # upgraded and the user hasn't logged in yet. In this case, we will force
- # the password to be updated.
- return true if current_resource.salt.nil?
-
- if salted_sha512_pbkdf2?(new_resource.password)
- diverged?(:password) || diverged?(:salt) || diverged?(:iterations)
- else
- !salted_sha512_pbkdf2_password_match?
- end
- end
-
- #
- # Returns true if user is member of the specified group, false otherwise.
- #
- def member_of_group?(group_name)
- membership_info = ""
- begin
- membership_info = run_dscl("read", "/Groups/#{group_name}")
- rescue Chef::Exceptions::DsclCommandFailed
- # Raised if the group doesn't contain any members
- end
- # Output is something like:
- # GroupMembership: root admin etc
- members = membership_info.split(" ")
- members.shift # Get rid of GroupMembership: string
- members.include?(new_resource.username)
- end
-
- #
- # DSCL Helper functions
- #
-
- # A simple map of Chef's terms to DSCL's terms.
- DSCL_PROPERTY_MAP = {
- uid: "uid",
- gid: "gid",
- home: "home",
- shell: "shell",
- comment: "realname",
- password: "passwd",
- auth_authority: "authentication_authority",
- shadow_hash: "ShadowHashData",
- }.freeze
-
- # Directory where the user plist files are stored for versions 10.7 and above
- USER_PLIST_DIRECTORY = "/var/db/dslocal/nodes/Default/users".freeze
-
- #
- # Reads the user plist and returns a hash keyed with DSCL properties specified
- # in DSCL_PROPERTY_MAP. Return nil if the user is not found.
- #
- def read_user_info
- user_info = nil
-
- # We flush the cache here in order to make sure that we read fresh information
- # for the user.
- shell_out("dscacheutil", "-flushcache") # FIXME: this is macOS version dependent
-
- begin
- user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist"
- user_plist_info = run_plutil("convert", "xml1", "-o", "-", user_plist_file)
- user_info = ::Plist.parse_xml(user_plist_info)
- rescue Chef::Exceptions::PlistUtilCommandFailed
- end
-
- user_info
- end
-
- #
- # Saves the given hash keyed with DSCL properties specified
- # in DSCL_PROPERTY_MAP to the disk.
- #
- def save_user_info(user_info)
- user_plist_file = "#{USER_PLIST_DIRECTORY}/#{new_resource.username}.plist"
- ::Plist::Emit.save_plist(user_info, user_plist_file)
- run_plutil("convert", "binary1", user_plist_file)
- end
-
- #
- # Sets a value in user information hash using Chef attributes as keys.
- #
- def dscl_set(user_hash, key, value)
- raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.key?(key)
-
- user_hash[DSCL_PROPERTY_MAP[key]] = [ value ]
- user_hash
- end
-
- #
- # Gets a value from user information hash using Chef attributes as keys.
- #
- def dscl_get(user_hash, key)
- raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.key?(key)
-
- # DSCL values are set as arrays
- value = user_hash[DSCL_PROPERTY_MAP[key]]
- value.nil? ? value : value.first
- end
-
- #
- # System Helpers
- #
-
- def run_dscl(*args)
- result = shell_out("dscl", ".", "-#{args[0]}", args[1..])
- 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.include?("No such key: ")
-
- result.stdout
- end
-
- def run_plutil(*args)
- result = shell_out("plutil", "-#{args[0]}", args[1..])
- raise(Chef::Exceptions::PlistUtilCommandFailed, "plutil error: #{result.inspect}") unless result.exitstatus == 0
-
- if result.stdout.encoding == Encoding::ASCII_8BIT
- result.stdout.encode("utf-8", "binary", undef: :replace, invalid: :replace, replace: "?")
- else
- result.stdout
- end
- end
-
- def convert_binary_plist_to_xml(binary_plist_string)
- shell_out("plutil", "-convert", "xml1", "-o", "-", "-", input: binary_plist_string).stdout
- end
-
- def convert_to_binary(string)
- string.unpack("a2" * (string.size / 2)).collect { |i| i.hex.chr }.join
- end
-
- def salted_sha512?(string)
- !!(string =~ /^[[:xdigit:]]{136}$/)
- end
-
- def salted_sha512_pbkdf2?(string)
- !!(string =~ /^[[:xdigit:]]{256}$/)
- end
-
- def salted_sha512_pbkdf2_password_match?
- salt = convert_to_binary(current_resource.salt)
-
- OpenSSL::PKCS5.pbkdf2_hmac(
- new_resource.password,
- salt,
- current_resource.iterations,
- 128,
- OpenSSL::Digest.new("SHA512")
- ).unpack("H*").first == current_resource.password
- end
-
- end
- end
- end
-end
diff --git a/lib/chef/provider/user/mac.rb b/lib/chef/provider/user/mac.rb
index aef831907a..74d9748d5b 100644
--- a/lib/chef/provider/user/mac.rb
+++ b/lib/chef/provider/user/mac.rb
@@ -29,13 +29,12 @@ class Chef
class User
# A macOS user provider that is compatible with default TCC restrictions
# in macOS 10.14. See resource/user/mac_user.rb for complete description
- # of the mac_user resource and how it differs from the dscl resource used
- # on previous platforms.
+ # of the mac_user resource
class MacUser < Chef::Provider::User
include Chef::Mixin::Which
provides :mac_user
- provides :user, os: "darwin", platform_version: ">= 10.14"
+ provides :user, os: "darwin"
attr_reader :user_plist, :admin_group_plist
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index 7652d60896..331f224855 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -94,7 +94,6 @@ require_relative "provider/service/aixinit"
require_relative "provider/service/aix"
require_relative "provider/user/aix"
-require_relative "provider/user/dscl"
require_relative "provider/user/linux"
require_relative "provider/user/mac"
require_relative "provider/user/pw"
diff --git a/lib/chef/resource/user/dscl_user.rb b/lib/chef/resource/user/dscl_user.rb
deleted file mode 100644
index 91efd657de..0000000000
--- a/lib/chef/resource/user/dscl_user.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-#
-# Copyright:: Copyright (c) Chef Software Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# 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_relative "../user"
-
-class Chef
- class Resource
- class User
- class DsclUser < Chef::Resource::User
- unified_mode true
-
- provides :dscl_user
- provides :user, platform: "mac_os_x", platform_version: "< 10.14"
-
- property :iterations, Integer,
- description: "macOS platform only. The number of iterations for a password with a SALTED-SHA512-PBKDF2 shadow hash.",
- default: 27855, desired_state: false
- end
- end
- end
-end
diff --git a/lib/chef/resource/user/mac_user.rb b/lib/chef/resource/user/mac_user.rb
index 2331283bbd..93eef5e893 100644
--- a/lib/chef/resource/user/mac_user.rb
+++ b/lib/chef/resource/user/mac_user.rb
@@ -61,7 +61,7 @@ class Chef
unified_mode true
provides :mac_user
- provides :user, platform: "mac_os_x", platform_version: ">= 10.14"
+ provides :user, platform: "mac_os_x"
introduced "15.3"
diff --git a/lib/chef/resources.rb b/lib/chef/resources.rb
index 843d5610b8..8ae922a28b 100644
--- a/lib/chef/resources.rb
+++ b/lib/chef/resources.rb
@@ -125,7 +125,6 @@ require_relative "resource/smartos_package"
require_relative "resource/template"
require_relative "resource/user"
require_relative "resource/user/aix_user"
-require_relative "resource/user/dscl_user"
require_relative "resource/user/linux_user"
require_relative "resource/user/mac_user"
require_relative "resource/user/pw_user"
diff --git a/spec/functional/resource/user/dscl_spec.rb b/spec/functional/resource/user/dscl_spec.rb
deleted file mode 100644
index 50da812b0f..0000000000
--- a/spec/functional/resource/user/dscl_spec.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-#
-# Copyright:: Copyright (c) Chef Software Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# 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 "spec_helper"
-require "chef/mixin/shell_out"
-
-metadata = {
- macos_1013: true,
- requires_root: true,
-}
-
-describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metadata do
- include Chef::Mixin::ShellOut
-
- def clean_user
- shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'")
- rescue Mixlib::ShellOut::ShellCommandFailed
- # Raised when the user is already cleaned
- end
-
- def user_should_exist
- expect(shell_out("/usr/bin/dscl . -ls /Users").stdout).to include username
- end
-
- def check_password(pass)
- # In order to test the password we use dscl passwd command since
- # that's the only command that gets the user password from CLI.
- expect(shell_out("dscl . -passwd /Users/greatchef #{pass} new_password").exitstatus).to eq(0)
- # Now reset the password back
- expect(shell_out("dscl . -passwd /Users/greatchef new_password #{pass}").exitstatus).to eq(0)
- end
-
- let(:node) do
- n = Chef::Node.new
- n.consume_external_attrs(OHAI_SYSTEM.data.dup, {})
- n
- end
-
- let(:events) do
- Chef::EventDispatch::Dispatcher.new
- end
-
- let(:run_context) do
- Chef::RunContext.new(node, {}, events)
- end
-
- let(:username) do
- "greatchef"
- end
-
- let(:uid) { nil }
- let(:gid) { 20 }
- let(:home) { nil }
- let(:manage_home) { false }
- let(:password) { "XXXYYYZZZ" }
- let(:comment) { "Great Chef" }
- let(:shell) { "/bin/bash" }
- let(:salt) { nil }
- let(:iterations) { nil }
-
- let(:user_resource) do
- r = Chef::Resource::User::DsclUser.new("TEST USER RESOURCE", run_context)
- r.username(username)
- r.uid(uid)
- r.gid(gid)
- r.home(home)
- r.shell(shell)
- r.comment(comment)
- r.manage_home(manage_home)
- r.password(password)
- r.salt(salt)
- r.iterations(iterations)
- r
- end
-
- before do
- clean_user
- end
-
- after(:each) do
- clean_user
- end
-
- describe "action :create" do
- it "should create the user" do
- user_resource.run_action(:create)
- user_should_exist
- check_password(password)
- end
- end
-
- describe "when user exists" do
- before do
- existing_resource = user_resource.dup
- existing_resource.run_action(:create)
- user_should_exist
- end
-
- describe "when password is updated" do
- it "should update the password of the user" do
- user_resource.password("mykitchen")
- user_resource.run_action(:create)
- check_password("mykitchen")
- end
- end
- end
-
- describe "when password is being set via shadow hash" do
- let(:password) do
- "c734b6e4787c3727bb35e29fdd92b97c\
-1de12df509577a045728255ec7c6c5f5\
-c18efa05ed02b682ffa7ebc05119900e\
-b1d4880833aa7a190afc13e2bf0936b8\
-20123e8c98f0f9bcac2a629d9163caac\
-9464a8c234f3919082400b4f939bb77b\
-c5adbbac718b7eb99463a7b679571e0f\
-1c9fef2ef08d0b9e9c2bcf644eed2ffc"
- end
-
- let(:iterations) { 25000 }
- let(:salt) { "9e2e7d5ee473b496fd24cf0bbfcaedfcb291ee21740e570d1e917e874f8788ca" }
-
- it "action :create should create the user" do
- user_resource.run_action(:create)
- user_should_exist
- check_password("soawesome")
- end
-
- describe "when user exists" do
- before do
- existing_resource = user_resource.dup
- existing_resource.run_action(:create)
- user_should_exist
- end
-
- describe "when password is updated" do
- it "should update the password of the user" do
- user_resource.password("mykitchen")
- user_resource.run_action(:create)
- check_password("mykitchen")
- end
- end
- end
- end
-
- describe "when a user is member of some groups" do
- let(:groups) { %w{staff operator} }
-
- before do
- existing_resource = user_resource.dup
- existing_resource.run_action(:create)
-
- groups.each do |group|
- shell_out!("/usr/bin/dscl . -append '/Groups/#{group}' GroupMembership #{username}")
- end
- end
-
- after do
- groups.each do |group|
- # Do not raise an error when user is correctly removed
- shell_out("/usr/bin/dscl . -delete '/Groups/#{group}' GroupMembership #{username}")
- end
- end
-
- it ":remove action removes the user from the groups and deletes the user" do
- user_resource.run_action(:remove)
- groups.each do |group|
- # Do not raise an error when group is empty
- expect(shell_out("dscl . read /Groups/staff GroupMembership").stdout).not_to include(group)
- end
- end
- end
-
-end
diff --git a/spec/functional/resource/user/mac_user_spec.rb b/spec/functional/resource/user/mac_user_spec.rb
index dabc303afb..32701da644 100644
--- a/spec/functional/resource/user/mac_user_spec.rb
+++ b/spec/functional/resource/user/mac_user_spec.rb
@@ -19,9 +19,9 @@ require "spec_helper"
require "chef/mixin/shell_out"
metadata = {
- macos_gte_1014: true,
requires_root: true,
-}
+ macos_only: true,
+ }
describe "Chef::Resource::User with Chef::Provider::User::MacUser provider", metadata do
include Chef::Mixin::ShellOut
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 4c925bace3..f439ccb54c 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -143,8 +143,6 @@ RSpec.configure do |config|
config.filter_run_excluding not_supported_on_windows: true if windows?
config.filter_run_excluding not_supported_on_macos: true if macos?
config.filter_run_excluding macos_only: true unless macos?
- config.filter_run_excluding macos_1013: true unless macos_1013?
- config.filter_run_excluding macos_gte_1014: true unless macos_gte_1014?
config.filter_run_excluding not_supported_on_aix: true if aix?
config.filter_run_excluding not_supported_on_solaris: true if solaris?
config.filter_run_excluding not_supported_on_gce: true if gce?
diff --git a/spec/support/platform_helpers.rb b/spec/support/platform_helpers.rb
index b29c860f30..83f2fcf172 100644
--- a/spec/support/platform_helpers.rb
+++ b/spec/support/platform_helpers.rb
@@ -92,14 +92,6 @@ def windows_user_right?(right)
Chef::ReservedNames::Win32::Security.get_account_right(ENV["USERNAME"]).include?(right)
end
-def macos_1013?
- macos? && Gem::Requirement.new("~> 10.13.0").satisfied_by?(Gem::Version.new(ohai[:platform_version]))
-end
-
-def macos_gte_1014?
- macos? && Gem::Requirement.new(">= 10.14").satisfied_by?(Gem::Version.new(ohai[:platform_version]))
-end
-
# detects if the hardware is 64-bit (evaluates to true in "WOW64" mode in a 32-bit app on a 64-bit system)
def windows64?
windows? && ( ENV["PROCESSOR_ARCHITECTURE"] == "AMD64" || ENV["PROCESSOR_ARCHITEW6432"] == "AMD64" )
diff --git a/spec/unit/provider/user/dscl_spec.rb b/spec/unit/provider/user/dscl_spec.rb
deleted file mode 100644
index 5652ae6868..0000000000
--- a/spec/unit/provider/user/dscl_spec.rb
+++ /dev/null
@@ -1,699 +0,0 @@
-#
-# Author:: Dreamcat4 (<dreamcat4@gmail.com>)
-# Copyright:: Copyright (c) Chef Software Inc.
-# License:: Apache License, Version 2.0
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# 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 "spec_helper"
-require "ostruct"
-
-describe Chef::Provider::User::Dscl do
- before do
- allow(ChefUtils).to receive(:windows?) { false }
- end
-
- let(:shellcmdresult) { Struct.new(:stdout, :stderr, :exitstatus) }
-
- let(:password) { nil }
- let(:salt) { nil }
- let(:iterations) { nil }
-
- let(:events) { Chef::EventDispatch::Dispatcher.new }
-
- let(:node) do
- Chef::Node.new.tap do |node|
- node.automatic["os"] = "darwin"
- node.automatic["platform_version"] = "10.13.0"
- end
- end
-
- let(:run_context) { Chef::RunContext.new(node, {}, events) }
-
- let(:new_resource) do
- r = Chef::Resource::User::DsclUser.new("toor", run_context)
- r.password(password)
- r.salt(salt)
- r.iterations(iterations)
- r
- end
-
- let(:provider) do
- Chef::Provider::User::Dscl.new(new_resource, run_context)
- end
-
- let(:salted_sha512_password) do
- "0f543f021c63255e64e121a3585601b8ecfedf6d2\
-705ddac69e682a33db5dbcdb9b56a2520bc8fff63a\
-2ba6b7984c0737ff0b7949455071581f7affcd536d\
-402b6cdb097"
- end
-
- let(:salted_sha512_pbkdf2_password) do
- "c734b6e4787c3727bb35e29fdd92b97c\
-1de12df509577a045728255ec7c6c5f5\
-c18efa05ed02b682ffa7ebc05119900e\
-b1d4880833aa7a190afc13e2bf0936b8\
-20123e8c98f0f9bcac2a629d9163caac\
-9464a8c234f3919082400b4f939bb77b\
-c5adbbac718b7eb99463a7b679571e0f\
-1c9fef2ef08d0b9e9c2bcf644eed2ffc"
- end
-
- let(:salted_sha512_pbkdf2_salt) do
- "2d942d8364a9ccf2b8e5cb7ed1ff58f78\
-e29dbfee7f9db58859144d061fd0058"
- end
-
- let(:salted_sha512_pbkdf2_iterations) do
- 25000
- end
-
- let(:vagrant_sha_512) do
- "6f75d7190441facc34291ebbea1fc756b242d4f\
-e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\
-ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30"
- end
-
- let(:vagrant_sha_512_pbkdf2) do
- "12601a90db17cbf\
-8ba4808e6382fb0d3b9d8a6c1a190477bf680ab21afb\
-6065467136e55cc208a6f74156e3daf20fb13369ef4b\
-7bafa047d80359fb46a48a4adccd548ebb33851b093\
-47cca84341a7f93a27147343f89fb843fb46c0017d2\
-64afa4976baacf941b915bd1ec1ca24c30b3e759e02\
-403e02f59fe7ff5938a7636c"
- end
-
- let(:vagrant_sha_512_pbkdf2_salt) do
- "ee954be472fdc60ddf89484781433993625f006af6ec810c08f49a7e413946a1"
- end
-
- let(:vagrant_sha_512_pbkdf2_iterations) do
- 34482
- end
-
- describe "when shelling out to dscl" do
- it "should run dscl with the supplied cmd /Path args" do
- shell_return = shellcmdresult.new("stdout", "err", 0)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-cmd", "/Path", "args").and_return(shell_return)
- expect(provider.run_dscl("cmd", "/Path", "args")).to eq("stdout")
- end
-
- it "returns an empty string from delete commands" do
- shell_return = shellcmdresult.new("out", "err", 23)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-delete", "/Path", "args").and_return(shell_return)
- expect(provider.run_dscl("delete", "/Path", "args")).to eq("")
- end
-
- it "should raise an exception for any other command" do
- shell_return = shellcmdresult.new("out", "err", 23)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-cmd", "/Path", "arguments").and_return(shell_return)
- expect { provider.run_dscl("cmd", "/Path", "arguments") }.to raise_error(Chef::Exceptions::DsclCommandFailed)
- end
-
- it "raises an exception when dscl reports 'no such key'" do
- shell_return = shellcmdresult.new("No such key: ", "err", 23)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-cmd", "/Path", "args").and_return(shell_return)
- expect { provider.run_dscl("cmd", "/Path", "args") }.to raise_error(Chef::Exceptions::DsclCommandFailed)
- end
-
- it "raises an exception when dscl reports 'eDSRecordNotFound'" do
- shell_return = shellcmdresult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", "err", -14136)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-cmd", "/Path", "args").and_return(shell_return)
- expect { provider.run_dscl("cmd", "/Path", "args") }.to raise_error(Chef::Exceptions::DsclCommandFailed)
- end
- end
-
- describe "get_free_uid" do
- before do
- expect(provider).to receive(:run_dscl).with("list", "/Users", "uid").and_return("\nwheel 200\nstaff 201\nbrahms 500\nchopin 501\n")
- end
-
- describe "when the system property is set to true" do
- before do
- new_resource.system(true)
- end
-
- it "should return the first unused uid number on or above 200" do
- expect(provider.get_free_uid).to eq(202)
- end
- end
-
- it "should return the first unused uid number on or above 500" do
- expect(provider.get_free_uid).to eq(502)
- end
-
- it "should raise an exception when the search limit is exhausted" do
- search_limit = 1
- expect { provider.get_free_uid(search_limit) }.to raise_error(RuntimeError)
- end
- end
-
- describe "uid_used?" do
- it "should return false if not given any valid uid number" do
- expect(provider.uid_used?(nil)).to be_falsey
- end
-
- describe "when called with a user id" do
- before do
- expect(provider).to receive(:run_dscl).with("list", "/Users", "uid").and_return("\naj 500\n")
- end
-
- it "should return true for a used uid number" do
- expect(provider.uid_used?(500)).to be_truthy
- end
-
- it "should return false for an unused uid number" do
- expect(provider.uid_used?(501)).to be_falsey
- end
- end
- end
-
- describe "when determining the uid to set" do
- it "raises RequestedUIDUnavailable if the requested uid is already in use" do
- allow(provider).to receive(:uid_used?).and_return(true)
- expect(provider).to receive(:get_free_uid).and_return(501)
- expect { provider.dscl_set_uid }.to raise_error(Chef::Exceptions::RequestedUIDUnavailable)
- end
-
- it "finds a valid, unused uid when none is specified" do
- expect(provider).to receive(:run_dscl).with("list", "/Users", "uid").and_return("")
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "UniqueID", 501)
- expect(provider).to receive(:get_free_uid).and_return(501)
- provider.dscl_set_uid
- expect(new_resource.uid).to eq(501)
- end
-
- it "sets the uid specified in the resource" do
- new_resource.uid(1000)
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "UniqueID", 1000).and_return(true)
- expect(provider).to receive(:run_dscl).with("list", "/Users", "uid").and_return("")
- provider.dscl_set_uid
- end
- end
-
- describe "current_home_exists?" do
- let(:current_resource) do
- new_resource.dup
- end
-
- before do
- provider.current_resource = current_resource
- end
-
- it "returns false for nil home dir" do
- current_resource.home nil
- expect(provider.current_home_exists?).to be_falsey
- end
-
- it "is false for empty string" do
- current_resource.home ""
- expect(provider.current_home_exists?).to be_falsey
- end
-
- it "is true for existing directory" do
- current_resource.home "/Users/blah"
- allow(::File).to receive(:exist?).with("/Users/blah").and_return(true)
- expect(provider.current_home_exists?).to be_truthy
- end
- end
-
- describe "when modifying the home directory" do
- let(:current_resource) do
- new_resource.dup
- end
-
- before do
- new_resource.manage_home true
- new_resource.home("/Users/toor")
-
- provider.current_resource = current_resource
- end
-
- it "deletes the home directory when resource#home is nil" do
- new_resource.instance_variable_set(:@home, nil)
- expect(provider).to receive(:run_dscl).with("delete", "/Users/toor", "NFSHomeDirectory").and_return(true)
- provider.dscl_set_home
- end
-
- it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do
- new_resource.home("epic-fail")
- expect { provider.dscl_set_home }.to raise_error(Chef::Exceptions::InvalidHomeDirectory)
- end
-
- it "moves the users home to the new location if it exists and the target location is different" do
- new_resource.manage_home true
-
- current_home = CHEF_SPEC_DATA + "/old_home_dir"
- current_home_files = [current_home + "/my-dot-emacs", current_home + "/my-dot-vim"]
- current_resource.home(current_home)
- new_resource.gid(23)
- allow(::File).to receive(:exist?).with("/old/home/toor").and_return(true)
- allow(::File).to receive(:exist?).with("/Users/toor").and_return(true)
- allow(::File).to receive(:exist?).with(current_home).and_return(true)
-
- expect(FileUtils).to receive(:mkdir_p).with("/Users/toor").and_return(true)
- expect(FileUtils).to receive(:rmdir).with(current_home)
- expect(::Dir).to receive(:glob).with("#{CHEF_SPEC_DATA}/old_home_dir/*", ::File::FNM_DOTMATCH).and_return(current_home_files)
- expect(FileUtils).to receive(:mv).with(current_home_files, "/Users/toor", force: true)
- expect(FileUtils).to receive(:chown_R).with("toor", "23", "/Users/toor")
-
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "NFSHomeDirectory", "/Users/toor")
- provider.dscl_set_home
- end
-
- it "should run createhomedir to create the user's new home folder" do
- expect(provider).to receive(:shell_out_compacted!).with("/usr/sbin/createhomedir", "-c", "-u", "toor")
- provider.ditto_home
- end
-
- it "creates the user's NFSHomeDirectory and home directory" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "NFSHomeDirectory", "/Users/toor").and_return(true)
- expect(provider).to receive(:ditto_home)
- provider.dscl_set_home
- end
- end
-
- describe "resource_requirements" do
- let(:dscl_exists) { true }
- let(:plutil_exists) { true }
-
- before do
- allow(::File).to receive(:exist?).with("/usr/bin/dscl").and_return(dscl_exists)
- allow(::File).to receive(:exist?).with("/usr/bin/plutil").and_return(plutil_exists)
- end
-
- def run_requirements
- provider.define_resource_requirements
- provider.action = :create
- provider.process_resource_requirements
- end
-
- describe "when dscl doesn't exist" do
- let(:dscl_exists) { false }
-
- it "should raise an error" do
- expect { run_requirements }.to raise_error(Chef::Exceptions::User)
- end
- end
-
- describe "when plutil doesn't exist" do
- let(:plutil_exists) { false }
-
- it "should raise an error" do
- expect { run_requirements }.to raise_error(Chef::Exceptions::User)
- end
- end
-
- describe "when password is SALTED-SHA512" do
- let(:password) { salted_sha512_password }
-
- it "should raise an error" do
- expect { run_requirements }.to raise_error(Chef::Exceptions::User)
- end
- end
-
- describe "when password is SALTED-SHA512-PBKDF2" do
- let(:password) { salted_sha512_pbkdf2_password }
-
- describe "when salt and iteration is not set" do
- it "should raise an error" do
- expect { run_requirements }.to raise_error(Chef::Exceptions::User)
- end
- end
-
- describe "when salt and iteration is set" do
- let(:salt) { salted_sha512_pbkdf2_salt }
- let(:iterations) { salted_sha512_pbkdf2_iterations }
-
- it "should not raise an error" do
- expect { run_requirements }.not_to raise_error
- end
- end
- end
- end
-
- describe "load_current_resource" do
- # set this to any of the user plist files under spec/data
- let(:user_plist_file) { nil }
-
- before do
- expect(provider).to receive(:shell_out_compacted).with("dscacheutil", "-flushcache")
- expect(provider).to receive(:shell_out_compacted).with("plutil", "-convert", "xml1", "-o", "-", "/var/db/dslocal/nodes/Default/users/toor.plist") do
- if user_plist_file.nil?
- shellcmdresult.new("Can not find the file", "Sorry!!", 1)
- else
- shellcmdresult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.plist.xml")), "", 0)
- end
- end
-
- unless user_plist_file.nil?
- expect(provider).to receive(:convert_binary_plist_to_xml).and_return(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.shadow.xml")))
- end
- end
-
- describe "when user is not there" do
- it "shouldn't raise an error" do
- expect { provider.load_current_resource }.not_to raise_error
- end
-
- it "should set @user_exists" do
- provider.load_current_resource
- expect(provider.instance_variable_get(:@user_exists)).to be_falsey
- end
-
- it "should set username" do
- provider.load_current_resource
- expect(provider.current_resource.username).to eq("toor")
- end
- end
-
- describe "when user is there" do
- let(:password) { "something" } # Load password during load_current_resource
-
- let(:user_plist_file) { "10.9" }
-
- it "collects the user data correctly" do
- provider.load_current_resource
- expect(provider.current_resource.comment).to eq("vagrant")
- expect(provider.current_resource.uid).to eq("501")
- expect(provider.current_resource.gid).to eq("80")
- expect(provider.current_resource.home).to eq("/Users/vagrant")
- expect(provider.current_resource.shell).to eq("/bin/bash")
- expect(provider.current_resource.password).to eq(vagrant_sha_512_pbkdf2)
- expect(provider.current_resource.salt).to eq(vagrant_sha_512_pbkdf2_salt)
- expect(provider.current_resource.iterations).to eq(vagrant_sha_512_pbkdf2_iterations)
- end
-
- describe "when a plain password is set that is same" do
- let(:password) { "vagrant" }
-
- it "diverged_password? should report false" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_falsey
- end
- end
-
- describe "when a plain password is set that is different" do
- let(:password) { "not_vagrant" }
-
- it "diverged_password? should report true" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_truthy
- end
- end
-
- describe "when iterations change" do
- let(:password) { vagrant_sha_512_pbkdf2 }
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
- let(:iterations) { 12345 }
-
- it "diverged_password? should report true" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_truthy
- end
- end
-
- describe "when shadow hash changes" do
- let(:password) { salted_sha512_pbkdf2_password }
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
-
- it "diverged_password? should report true" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_truthy
- end
- end
-
- describe "when salt change" do
- let(:password) { vagrant_sha_512_pbkdf2 }
- let(:salt) { salted_sha512_pbkdf2_salt }
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
-
- it "diverged_password? should report true" do
- provider.load_current_resource
- expect(provider.diverged_password?).to be_truthy
- end
- end
-
- describe "when salt isn't found" do
- it "diverged_password? should report true" do
- provider.load_current_resource
- provider.current_resource.salt(nil)
- expect(provider.diverged_password?).to be_truthy
- end
- end
- end
- end
-
- describe "salted_sha512_pbkdf2?" do
- it "should return true when the string is a salted_sha512_pbkdf2 hash" do
- expect(provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password)).to be_truthy
- end
-
- it "should return false otherwise" do
- expect(provider.salted_sha512_pbkdf2?(salted_sha512_password)).to be_falsey
- expect(provider.salted_sha512_pbkdf2?("any other string")).to be_falsey
- end
- end
-
- describe "salted_sha512?" do
- it "should return true when the string is a salted_sha512_pbkdf2 hash" do
- expect(provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password)).to be_truthy
- end
-
- it "should return false otherwise" do
- expect(provider.salted_sha512?(salted_sha512_pbkdf2_password)).to be_falsey
- expect(provider.salted_sha512?("any other string")).to be_falsey
- end
- end
-
- describe "prepare_password_shadow_info" do
- describe "when the password is plain text" do
- let(:password) { "vagrant" }
-
- it "password_shadow_info should have salted-sha-512 format" do
- shadow_info = provider.prepare_password_shadow_info
- expect(shadow_info).to have_key("SALTED-SHA512-PBKDF2")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("entropy")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("salt")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("iterations")
- info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first
- expect(provider.salted_sha512_pbkdf2?(info)).to be_truthy
- end
- end
-
- describe "when the password is salted-sha-512" do
- let(:password) { vagrant_sha_512_pbkdf2 }
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
-
- it "password_shadow_info should have salted-sha-512 format" do
- shadow_info = provider.prepare_password_shadow_info
- expect(shadow_info).to have_key("SALTED-SHA512-PBKDF2")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("entropy")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("salt")
- expect(shadow_info["SALTED-SHA512-PBKDF2"]).to have_key("iterations")
- info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack("H*").first
- expect(provider.salted_sha512_pbkdf2?(info)).to be_truthy
- expect(info).to eq(vagrant_sha_512_pbkdf2)
- end
- end
- end
-
- describe "set_password" do
- before do
- new_resource.password("something")
- end
-
- it "should sleep and flush the dscl cache before saving the password" do
- expect(provider).to receive(:prepare_password_shadow_info).and_return({})
- mock_shellout = double("Mock::Shellout")
- allow(mock_shellout).to receive(:run_command)
- expect(provider).to receive(:shell_out_compacted).and_return(mock_shellout)
- expect(provider).to receive(:read_user_info)
- expect(provider).to receive(:dscl_set)
- expect(provider).to receive(:sleep).with(3)
- expect(provider).to receive(:save_user_info)
- provider.set_password
- end
- end
-
- describe "when the user does not yet exist and chef is creating it" do
- context "with a numeric gid" do
- before do
- new_resource.comment "#mockssuck"
- new_resource.gid 1001
- end
-
- it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
- expect(provider).to receive :dscl_create_user
- expect(provider).to receive :dscl_create_comment
- expect(provider).to receive :dscl_set_uid
- expect(provider).to receive :dscl_set_gid
- expect(provider).to receive :dscl_set_home
- expect(provider).to receive :dscl_set_shell
- expect(provider).to receive :set_password
- provider.create_user
- end
-
- it "creates the user and sets the comment field" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor").and_return(true)
- provider.dscl_create_user
- end
-
- it "sets the comment field" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "RealName", "#mockssuck").and_return(true)
- provider.dscl_create_comment
- end
-
- it "sets the comment field to username" do
- new_resource.comment nil
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "RealName", "toor").and_return(true)
- provider.dscl_create_comment
- end
-
- it "should run run_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "PrimaryGroupID", 1001).and_return(true)
- provider.dscl_set_gid
- end
-
- it "should run run_dscl with create /Users/user UserShell to set the users login shell" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "UserShell", "/usr/bin/false").and_return(true)
- provider.dscl_set_shell
- end
- end
-
- context "with a non-numeric gid" do
- before do
- new_resource.comment "#mockssuck"
- new_resource.gid "newgroup"
- end
-
- it "should map the group name to a numeric ID when the group exists" do
- expect(provider).to receive(:run_dscl).with("read", "/Groups/newgroup", "PrimaryGroupID").ordered.and_return("PrimaryGroupID: 1001\n")
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "PrimaryGroupID", "1001").ordered.and_return(true)
- provider.dscl_set_gid
- end
-
- it "should raise an exception when the group does not exist" do
- shell_return = shellcmdresult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", "err", -14136)
- expect(provider).to receive(:shell_out_compacted).with("dscl", ".", "-read", "/Groups/newgroup", "PrimaryGroupID").and_return(shell_return)
- expect { provider.dscl_set_gid }.to raise_error(Chef::Exceptions::GroupIDNotFound)
- end
- end
-
- it "should set group ID to 20 if it's not specified" do
- new_resource.gid nil
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "PrimaryGroupID", 20).ordered.and_return(true)
- provider.dscl_set_gid
- expect(new_resource.gid).to eq(20)
- end
- end
-
- describe "when the user exists and chef is managing it" do
- before do
- current_resource = new_resource.dup
- provider.current_resource = current_resource
-
- # These are all different from current_resource
- new_resource.username "mud"
- new_resource.uid 2342
- new_resource.gid 2342
- new_resource.home "/Users/death"
- new_resource.password "goaway"
- end
-
- it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do
- expect(provider).to receive :dscl_create_user
- expect(provider).to receive :dscl_create_comment
- expect(provider).to receive :dscl_set_uid
- expect(provider).to receive :dscl_set_gid
- expect(provider).to receive :dscl_set_home
- expect(provider).to receive :dscl_set_shell
- expect(provider).to receive :set_password
- provider.create_user
- end
- end
-
- describe "when changing the gid" do
- before do
- current_resource = new_resource.dup
- provider.current_resource = current_resource
-
- # This is different from current_resource
- new_resource.gid 2342
- end
-
- it "sets the gid" do
- expect(provider).to receive :dscl_set_gid
- provider.manage_user
- end
- end
-
- describe "when the user exists" do
- before do
- expect(provider).to receive(:shell_out_compacted).with("dscacheutil", "-flushcache")
- expect(provider).to receive(:shell_out_compacted).with("plutil", "-convert", "xml1", "-o", "-", "/var/db/dslocal/nodes/Default/users/toor.plist") do
- shellcmdresult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/10.9.plist.xml")), "", 0)
- end
- provider.load_current_resource
- end
-
- describe "when Chef is removing the user" do
- it "removes the user from the groups and deletes home directory when the resource is configured to manage home" do
- new_resource.manage_home true
- expect(provider).to receive(:run_dscl).with("list", "/Groups").and_return("my_group\nyour_group\nreal_group\n")
- expect(provider).to receive(:run_dscl).with("read", "/Groups/my_group").and_raise(Chef::Exceptions::DsclCommandFailed) # Empty group
- expect(provider).to receive(:run_dscl).with("read", "/Groups/your_group").and_return("GroupMembership: not_you")
- expect(provider).to receive(:run_dscl).with("read", "/Groups/real_group").and_return("GroupMembership: toor")
- expect(provider).to receive(:run_dscl).with("delete", "/Groups/real_group", "GroupMembership", "toor")
- expect(provider).to receive(:run_dscl).with("delete", "/Users/toor")
- expect(FileUtils).to receive(:rm_rf).with("/Users/vagrant")
- provider.remove_user
- end
- end
-
- describe "when user is not locked" do
- it "determines the user as not locked" do
- expect(provider).not_to be_locked
- end
- end
-
- describe "when user is locked" do
- before do
- auth_authority = provider.instance_variable_get(:@authentication_authority)
- provider.instance_variable_set(:@authentication_authority, auth_authority + ";DisabledUser;")
- end
-
- it "determines the user as not locked" do
- expect(provider).to be_locked
- end
-
- it "can unlock the user" do
- expect(provider).to receive(:run_dscl).with("create", "/Users/toor", "AuthenticationAuthority", ";ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2>")
- provider.unlock_user
- end
- end
- end
-
- describe "when locking the user" do
- it "should run run_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
- expect(provider).to receive(:run_dscl).with("append", "/Users/toor", "AuthenticationAuthority", ";DisabledUser;")
- provider.lock_user
- end
- end
-
-end
diff --git a/tasks/docs.rb b/tasks/docs.rb
index efb0d5420f..98261a7402 100755
--- a/tasks/docs.rb
+++ b/tasks/docs.rb
@@ -1,4 +1,4 @@
-RESOURCES_TO_SKIP = ["whyrun_safe_ruby_block", "l_w_r_p_base", "user_resource_abstract_base_class", "linux_user", "pw_user", "aix_user", "dscl_user", "solaris_user", "windows_user", "mac_user", ""].freeze
+RESOURCES_TO_SKIP = ["whyrun_safe_ruby_block", "l_w_r_p_base", "user_resource_abstract_base_class", "linux_user", "pw_user", "aix_user", "solaris_user", "windows_user", "mac_user", ""].freeze
namespace :docs_site do