chef 12.0.0.alpha.0 → 12.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (207) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +3 -5
  3. data/lib/chef/api_client.rb +1 -1
  4. data/lib/chef/application.rb +16 -8
  5. data/lib/chef/chef_fs/chef_fs_data_store.rb +1 -1
  6. data/lib/chef/chef_fs/command_line.rb +1 -1
  7. data/lib/chef/chef_fs/file_system.rb +1 -1
  8. data/lib/chef/chef_fs/file_system/acl_entry.rb +1 -1
  9. data/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb +3 -3
  10. data/lib/chef/chef_fs/file_system/cookbook_file.rb +2 -2
  11. data/lib/chef/chef_fs/file_system/rest_list_dir.rb +2 -2
  12. data/lib/chef/chef_fs/file_system/rest_list_entry.rb +4 -4
  13. data/lib/chef/config.rb +6 -5
  14. data/lib/chef/config_fetcher.rb +1 -1
  15. data/lib/chef/cookbook/cookbook_version_loader.rb +126 -43
  16. data/lib/chef/cookbook/metadata.rb +102 -53
  17. data/lib/chef/cookbook/syntax_check.rb +1 -1
  18. data/lib/chef/cookbook_loader.rb +62 -14
  19. data/lib/chef/cookbook_site_streaming_uploader.rb +12 -1
  20. data/lib/chef/cookbook_version.rb +13 -4
  21. data/lib/chef/data_bag.rb +28 -15
  22. data/lib/chef/data_bag_item.rb +5 -7
  23. data/lib/chef/digester.rb +5 -9
  24. data/lib/chef/dsl/recipe.rb +14 -0
  25. data/lib/chef/encrypted_data_bag_item.rb +1 -0
  26. data/lib/chef/encrypted_data_bag_item/assertions.rb +57 -0
  27. data/lib/chef/encrypted_data_bag_item/decryptor.rb +52 -28
  28. data/lib/chef/encrypted_data_bag_item/encrypted_data_bag_item_assertions.rb +37 -0
  29. data/lib/chef/encrypted_data_bag_item/encryption_failure.rb +22 -0
  30. data/lib/chef/encrypted_data_bag_item/encryptor.rb +79 -8
  31. data/lib/chef/environment.rb +1 -3
  32. data/lib/chef/exceptions.rb +18 -3
  33. data/lib/chef/formatters/base.rb +7 -0
  34. data/lib/chef/formatters/error_inspectors/cookbook_resolve_error_inspector.rb +1 -1
  35. data/lib/chef/handler/json_file.rb +0 -1
  36. data/lib/chef/http/json_output.rb +1 -1
  37. data/lib/chef/json_compat.rb +24 -6
  38. data/lib/chef/knife/bootstrap.rb +2 -2
  39. data/lib/chef/knife/client_delete.rb +1 -1
  40. data/lib/chef/knife/cookbook_site_download.rb +1 -1
  41. data/lib/chef/knife/cookbook_site_list.rb +1 -1
  42. data/lib/chef/knife/cookbook_site_search.rb +1 -1
  43. data/lib/chef/knife/cookbook_site_share.rb +2 -2
  44. data/lib/chef/knife/cookbook_site_show.rb +3 -3
  45. data/lib/chef/knife/cookbook_site_unshare.rb +1 -1
  46. data/lib/chef/knife/core/node_editor.rb +2 -3
  47. data/lib/chef/knife/core/ui.rb +2 -2
  48. data/lib/chef/knife/deps.rb +2 -3
  49. data/lib/chef/mixin/shell_out.rb +1 -1
  50. data/lib/chef/mixin/windows_architecture_helper.rb +1 -0
  51. data/lib/chef/node.rb +1 -2
  52. data/lib/chef/platform/provider_mapping.rb +33 -6
  53. data/lib/chef/provider.rb +0 -2
  54. data/lib/chef/provider/cookbook_file/content.rb +1 -1
  55. data/lib/chef/provider/cron.rb +11 -0
  56. data/lib/chef/provider/deploy.rb +3 -2
  57. data/lib/chef/provider/deploy/revision.rb +2 -2
  58. data/lib/chef/provider/env.rb +1 -1
  59. data/lib/chef/provider/env/windows.rb +5 -9
  60. data/lib/chef/provider/file.rb +84 -33
  61. data/lib/chef/provider/git.rb +2 -1
  62. data/lib/chef/provider/group/aix.rb +17 -2
  63. data/lib/chef/provider/group/dscl.rb +27 -9
  64. data/lib/chef/provider/group/pw.rb +8 -1
  65. data/lib/chef/provider/http_request.rb +4 -4
  66. data/lib/chef/provider/log.rb +4 -14
  67. data/lib/chef/provider/mount/mount.rb +2 -2
  68. data/lib/chef/provider/package/ips.rb +17 -23
  69. data/lib/chef/provider/package/paludis.rb +2 -2
  70. data/lib/chef/provider/package/rpm.rb +2 -2
  71. data/lib/chef/provider/package/rubygems.rb +2 -0
  72. data/lib/chef/provider/package/yum.rb +2 -0
  73. data/lib/chef/provider/package/zypper.rb +1 -1
  74. data/lib/chef/provider/remote_file/cache_control_data.rb +2 -2
  75. data/lib/chef/provider/service/windows.rb +87 -21
  76. data/lib/chef/provider/user/aix.rb +95 -0
  77. data/lib/chef/provider/user/dscl.rb +544 -156
  78. data/lib/chef/provider/user/useradd.rb +1 -0
  79. data/lib/chef/providers.rb +1 -0
  80. data/lib/chef/resource.rb +4 -3
  81. data/lib/chef/resource/freebsd_package.rb +10 -2
  82. data/lib/chef/resource/paludis_package.rb +1 -0
  83. data/lib/chef/resource/scm.rb +10 -0
  84. data/lib/chef/resource/user.rb +27 -0
  85. data/lib/chef/resource/windows_service.rb +53 -0
  86. data/lib/chef/resource_collection.rb +23 -12
  87. data/lib/chef/resource_reporter.rb +10 -10
  88. data/lib/chef/resources.rb +1 -0
  89. data/lib/chef/role.rb +3 -3
  90. data/lib/chef/run_list.rb +6 -3
  91. data/lib/chef/user.rb +1 -1
  92. data/lib/chef/util/diff.rb +1 -2
  93. data/lib/chef/version.rb +1 -1
  94. data/lib/chef/version_constraint.rb +4 -4
  95. data/spec/data/cookbooks/angrybash/metadata.rb +2 -0
  96. data/spec/data/cookbooks/apache2/metadata.rb +2 -0
  97. data/spec/data/cookbooks/borken/metadata.rb +2 -0
  98. data/spec/data/cookbooks/ignorken/metadata.rb +2 -0
  99. data/spec/data/cookbooks/java/metadata.rb +2 -0
  100. data/spec/data/cookbooks/name-mismatch-versionnumber/README.md +4 -0
  101. data/spec/data/cookbooks/name-mismatch-versionnumber/metadata.rb +8 -0
  102. data/spec/data/cookbooks/name-mismatch-versionnumber/recipes/default.rb +8 -0
  103. data/spec/data/cookbooks/openldap/files/default/remotedir/not_a_template.erb +2 -0
  104. data/spec/data/cookbooks/preseed/metadata.rb +2 -0
  105. data/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/README.md +4 -0
  106. data/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/metadata.rb +13 -0
  107. data/spec/data/incomplete-metadata-chef-repo/incomplete-metadata/recipes/default.rb +8 -0
  108. data/spec/data/invalid-metadata-chef-repo/invalid-metadata/README.md +4 -0
  109. data/spec/data/invalid-metadata-chef-repo/invalid-metadata/metadata.rb +10 -0
  110. data/spec/data/invalid-metadata-chef-repo/invalid-metadata/recipes/default.rb +8 -0
  111. data/spec/data/mac_users/10.7-8.plist.xml +559 -0
  112. data/spec/data/mac_users/10.7-8.shadow.xml +11 -0
  113. data/spec/data/mac_users/10.7.plist.xml +559 -0
  114. data/spec/data/mac_users/10.7.shadow.xml +11 -0
  115. data/spec/data/mac_users/10.8.plist.xml +559 -0
  116. data/spec/data/mac_users/10.8.shadow.xml +21 -0
  117. data/spec/data/mac_users/10.9.plist.xml +560 -0
  118. data/spec/data/mac_users/10.9.shadow.xml +21 -0
  119. data/spec/data/object_loader/environments/test.json +2 -0
  120. data/spec/data/object_loader/environments/test_json_class.json +2 -0
  121. data/spec/data/object_loader/nodes/test.json +2 -0
  122. data/spec/data/object_loader/nodes/test_json_class.json +2 -0
  123. data/spec/data/object_loader/roles/test.json +2 -0
  124. data/spec/data/object_loader/roles/test_json_class.json +2 -0
  125. data/spec/functional/resource/bff_spec.rb +1 -1
  126. data/spec/functional/resource/cron_spec.rb +20 -1
  127. data/spec/functional/resource/env_spec.rb +137 -0
  128. data/spec/functional/resource/group_spec.rb +7 -5
  129. data/spec/functional/resource/remote_file_spec.rb +12 -1
  130. data/spec/functional/resource/user/dscl_spec.rb +198 -0
  131. data/spec/functional/resource/{user_spec.rb → user/useradd_spec.rb} +175 -37
  132. data/spec/integration/client/client_spec.rb +6 -4
  133. data/spec/integration/client/ipv6_spec.rb +16 -14
  134. data/spec/integration/knife/chef_fs_data_store_spec.rb +57 -46
  135. data/spec/integration/knife/chef_repo_path_spec.rb +105 -78
  136. data/spec/integration/knife/chef_repository_file_system_spec.rb +100 -84
  137. data/spec/integration/knife/chefignore_spec.rb +76 -46
  138. data/spec/integration/knife/common_options_spec.rb +16 -21
  139. data/spec/integration/knife/cookbook_api_ipv6_spec.rb +3 -3
  140. data/spec/integration/knife/delete_spec.rb +66 -46
  141. data/spec/integration/knife/deps_spec.rb +145 -94
  142. data/spec/integration/knife/diff_spec.rb +176 -110
  143. data/spec/integration/knife/download_spec.rb +229 -133
  144. data/spec/integration/knife/list_spec.rb +62 -54
  145. data/spec/integration/knife/raw_spec.rb +24 -9
  146. data/spec/integration/knife/redirection_spec.rb +2 -2
  147. data/spec/integration/knife/serve_spec.rb +2 -2
  148. data/spec/integration/knife/show_spec.rb +32 -26
  149. data/spec/integration/knife/upload_spec.rb +308 -165
  150. data/spec/integration/recipes/lwrp_inline_resources_spec.rb +10 -8
  151. data/spec/integration/solo/solo_spec.rb +22 -11
  152. data/spec/spec_helper.rb +3 -0
  153. data/spec/support/lib/chef/resource/zen_follower.rb +46 -0
  154. data/spec/support/platform_helpers.rb +12 -0
  155. data/spec/support/shared/functional/file_resource.rb +10 -0
  156. data/spec/support/shared/integration/chef_zero_support.rb +130 -0
  157. data/spec/support/shared/integration/integration_helper.rb +100 -98
  158. data/spec/support/shared/integration/knife_support.rb +0 -1
  159. data/spec/support/shared/unit/provider/file.rb +6 -4
  160. data/spec/support/shared/unit/provider/useradd_based_user_provider.rb +10 -1
  161. data/spec/unit/api_client/registration_spec.rb +83 -74
  162. data/spec/unit/application_spec.rb +32 -9
  163. data/spec/unit/cookbook/cookbook_version_loader_spec.rb +179 -0
  164. data/spec/unit/cookbook/metadata_spec.rb +190 -150
  165. data/spec/unit/cookbook/syntax_check_spec.rb +3 -2
  166. data/spec/unit/cookbook_loader_spec.rb +114 -53
  167. data/spec/unit/{cookbook_site_streaming_uploader.rb → cookbook_site_streaming_uploader_spec.rb} +21 -1
  168. data/spec/unit/data_bag_spec.rb +88 -13
  169. data/spec/unit/deprecation_spec.rb +1 -2
  170. data/spec/unit/encrypted_data_bag_item_spec.rb +145 -9
  171. data/spec/unit/environment_spec.rb +1 -1
  172. data/spec/unit/formatters/base_spec.rb +48 -0
  173. data/spec/unit/json_compat_spec.rb +48 -17
  174. data/spec/unit/knife/client_delete_spec.rb +4 -4
  175. data/spec/unit/knife/client_show_spec.rb +15 -5
  176. data/spec/unit/knife/cookbook_site_download_spec.rb +1 -1
  177. data/spec/unit/knife/cookbook_site_share_spec.rb +3 -3
  178. data/spec/unit/knife/data_bag_from_file_spec.rb +0 -2
  179. data/spec/unit/knife/data_bag_show_spec.rb +23 -14
  180. data/spec/unit/knife/node_show_spec.rb +32 -15
  181. data/spec/unit/knife/role_show_spec.rb +59 -0
  182. data/spec/unit/platform_spec.rb +10 -0
  183. data/spec/unit/provider/deploy_spec.rb +4 -0
  184. data/spec/unit/provider/env_spec.rb +19 -0
  185. data/spec/unit/provider/git_spec.rb +22 -2
  186. data/spec/unit/provider/group/dscl_spec.rb +38 -1
  187. data/spec/unit/provider/group/pw_spec.rb +2 -2
  188. data/spec/unit/provider/http_request_spec.rb +8 -8
  189. data/spec/unit/provider/log_spec.rb +33 -53
  190. data/spec/unit/provider/mount/mount_spec.rb +12 -3
  191. data/spec/unit/provider/package/ips_spec.rb +96 -63
  192. data/spec/unit/provider/package/paludis_spec.rb +5 -5
  193. data/spec/unit/provider/package/rpm_spec.rb +12 -0
  194. data/spec/unit/provider/package/zypper_spec.rb +28 -16
  195. data/spec/unit/provider/service/windows_spec.rb +77 -17
  196. data/spec/unit/provider/user/dscl_spec.rb +659 -264
  197. data/spec/unit/provider/user/useradd_spec.rb +1 -0
  198. data/spec/unit/recipe_spec.rb +41 -0
  199. data/spec/unit/resource/scm_spec.rb +11 -0
  200. data/spec/unit/resource/user_spec.rb +4 -0
  201. data/spec/unit/resource/windows_service_spec.rb +46 -0
  202. data/spec/unit/resource_collection_spec.rb +33 -0
  203. data/spec/unit/resource_reporter_spec.rb +48 -0
  204. data/spec/unit/resource_spec.rb +9 -2
  205. data/spec/unit/role_spec.rb +6 -0
  206. data/spec/unit/version_constraint_spec.rb +28 -0
  207. metadata +61 -4
@@ -0,0 +1,95 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2012 Opscode, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ class Chef
18
+ class Provider
19
+ class User
20
+ class Aix < Chef::Provider::User::Useradd
21
+
22
+ UNIVERSAL_OPTIONS = [[:comment, "-c"], [:gid, "-g"], [:shell, "-s"], [:uid, "-u"]]
23
+
24
+ def create_user
25
+ super
26
+ add_password
27
+ end
28
+
29
+ def manage_user
30
+ add_password
31
+ manage_home
32
+ super
33
+ end
34
+
35
+ # Aix does not support -r like other unix, sytem account is created by adding to 'system' group
36
+ def useradd_options
37
+ opts = []
38
+ opts << "-g" << "system" if new_resource.system
39
+ opts
40
+ end
41
+
42
+ def check_lock
43
+ lock_info = shell_out!("lsuser -a account_locked #{new_resource.username}")
44
+ if whyrun_mode? && passwd_s.stdout.empty? && lock_info.stderr.match(/does not exist/)
45
+ # if we're in whyrun mode and the user is not yet created we assume it would be
46
+ return false
47
+ end
48
+ raise Chef::Exceptions::User, "Cannot determine if #{@new_resource} is locked!" if lock_info.stdout.empty?
49
+
50
+ status = /\S+\s+account_locked=(\S+)/.match(lock_info.stdout)
51
+ if status && status[1] == "true"
52
+ @locked = true
53
+ else
54
+ @locked = false
55
+ end
56
+
57
+ @locked
58
+ end
59
+
60
+ def lock_user
61
+ shell_out!("chuser account_locked=true #{new_resource.username}")
62
+ end
63
+
64
+ def unlock_user
65
+ shell_out!("chuser account_locked=false #{new_resource.username}")
66
+ end
67
+
68
+ private
69
+ def add_password
70
+ if @current_resource.password != @new_resource.password && @new_resource.password
71
+ Chef::Log.debug("#{@new_resource.username} setting password to #{@new_resource.password}")
72
+ command = "echo '#{@new_resource.username}:#{@new_resource.password}' | chpasswd -e"
73
+ shell_out!(command)
74
+ end
75
+ end
76
+
77
+ # Aix specific handling to update users home directory.
78
+ def manage_home
79
+ # -m option does not work on aix, so move dir.
80
+ if updating_home? and managing_home_dir?
81
+ universal_options.delete("-m")
82
+ if ::File.directory?(@current_resource.home)
83
+ Chef::Log.debug("Changing users home directory from #{@current_resource.home} to #{new_resource.home}")
84
+ shell_out!("mv #{@current_resource.home} #{new_resource.home}")
85
+ else
86
+ Chef::Log.debug("Creating users home directory #{new_resource.home}")
87
+ shell_out!("mkdir -p #{new_resource.home}")
88
+ end
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
95
+ end
@@ -16,40 +16,209 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
+ require 'mixlib/shellout'
19
20
  require 'chef/provider/user'
20
21
  require 'openssl'
22
+ require 'plist'
21
23
 
22
24
  class Chef
23
25
  class Provider
24
26
  class User
27
+ #
28
+ # The most tricky bit of this provider is the way it deals with user passwords.
29
+ # Mac OS X has different password shadow calculations based on the version.
30
+ # < 10.7 => password shadow calculation format SALTED-SHA1
31
+ # => stored in: /var/db/shadow/hash/#{guid}
32
+ # => shadow binary length 68 bytes
33
+ # => First 4 bytes salt / Next 64 bytes shadow value
34
+ # = 10.7 => password shadow calculation format SALTED-SHA512
35
+ # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist
36
+ # => shadow binary length 68 bytes
37
+ # => First 4 bytes salt / Next 64 bytes shadow value
38
+ # > 10.7 => password shadow calculation format SALTED-SHA512-PBKDF2
39
+ # => stored in: /var/db/dslocal/nodes/Default/users/#{name}.plist
40
+ # => shadow binary length 128 bytes
41
+ # => Salt / Iterations are stored seperately in the same file
42
+ #
43
+ # This provider only supports Mac OSX versions 10.7 and above
25
44
  class Dscl < Chef::Provider::User
26
45
 
27
- NFS_HOME_DIRECTORY = %r{^NFSHomeDirectory: (.*)$}
28
- AUTHENTICATION_AUTHORITY = %r{^AuthenticationAuthority: (.*)$}
46
+ def define_resource_requirements
47
+ super
48
+
49
+ requirements.assert(:all_actions) do |a|
50
+ a.assertion { mac_osx_version_less_than_10_7? == false }
51
+ a.failure_message(Chef::Exceptions::User, "Chef::Provider::User::Dscl only supports Mac OS X versions 10.7 and above.")
52
+ end
53
+
54
+ requirements.assert(:all_actions) do |a|
55
+ a.assertion { ::File.exists?("/usr/bin/dscl") }
56
+ a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/dscl' on the system for #{@new_resource}!")
57
+ end
58
+
59
+ requirements.assert(:all_actions) do |a|
60
+ a.assertion { ::File.exists?("/usr/bin/plutil") }
61
+ a.failure_message(Chef::Exceptions::User, "Cannot find binary '/usr/bin/plutil' on the system for #{@new_resource}!")
62
+ end
63
+
64
+ requirements.assert(:create, :modify, :manage) do |a|
65
+ a.assertion do
66
+ if @new_resource.password && mac_osx_version_greater_than_10_7?
67
+ # SALTED-SHA512 password shadow hashes are not supported on 10.8 and above.
68
+ !salted_sha512?(@new_resource.password)
69
+ else
70
+ true
71
+ end
72
+ end
73
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512 passwords are not supported on Mac 10.8 and above. \
74
+ If you want to set the user password using shadow info make sure you specify a SALTED-SHA512-PBKDF2 shadow hash \
75
+ in 'password', with the associated 'salt' and 'iterations'.")
76
+ end
77
+
78
+ requirements.assert(:create, :modify, :manage) do |a|
79
+ a.assertion do
80
+ if @new_resource.password && mac_osx_version_greater_than_10_7? && salted_sha512_pbkdf2?(@new_resource.password)
81
+ # salt and iterations should be specified when
82
+ # SALTED-SHA512-PBKDF2 password shadow hash is given
83
+ !@new_resource.salt.nil? && !@new_resource.iterations.nil?
84
+ else
85
+ true
86
+ end
87
+ end
88
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hash is given without associated \
89
+ 'salt' and 'iterations'. Please specify 'salt' and 'iterations' in order to set the user password using shadow hash.")
90
+ end
91
+
92
+ requirements.assert(:create, :modify, :manage) do |a|
93
+ a.assertion do
94
+ if @new_resource.password && !mac_osx_version_greater_than_10_7?
95
+ # On 10.7 SALTED-SHA512-PBKDF2 is not supported
96
+ !salted_sha512_pbkdf2?(@new_resource.password)
97
+ else
98
+ true
99
+ end
100
+ end
101
+ a.failure_message(Chef::Exceptions::User, "SALTED-SHA512-PBKDF2 shadow hashes are not supported on \
102
+ Mac OS X version 10.7. Please specify a SALTED-SHA512 shadow hash in 'password' attribute to set the \
103
+ user password using shadow hash.")
104
+ end
29
105
 
30
- def dscl(*args)
31
- shell_out("dscl . -#{args.join(' ')}")
32
106
  end
33
107
 
34
- def safe_dscl(*args)
35
- result = dscl(*args)
36
- return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
37
- raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") unless result.exitstatus == 0
38
- raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
39
- return result.stdout
108
+ def load_current_resource
109
+ @current_resource = Chef::Resource::User.new(@new_resource.username)
110
+ @current_resource.username(@new_resource.username)
111
+
112
+ user_info = read_user_info
113
+ if user_info
114
+ @current_resource.uid(dscl_get(user_info, :uid))
115
+ @current_resource.gid(dscl_get(user_info, :gid))
116
+ @current_resource.home(dscl_get(user_info, :home))
117
+ @current_resource.shell(dscl_get(user_info, :shell))
118
+ @current_resource.comment(dscl_get(user_info, :comment))
119
+ @authentication_authority = dscl_get(user_info, :auth_authority)
120
+
121
+ if @new_resource.password && dscl_get(user_info, :password) == "********"
122
+ # A password is set. Let's get the password information from shadow file
123
+ shadow_hash_binary = dscl_get(user_info, :shadow_hash)
124
+
125
+ # Calling shell_out directly since we want to give an input stream
126
+ shadow_hash_xml = convert_binary_plist_to_xml(shadow_hash_binary.string)
127
+ shadow_hash = Plist::parse_xml(shadow_hash_xml)
128
+
129
+ if shadow_hash["SALTED-SHA512"]
130
+ # Convert the shadow value from Base64 encoding to hex before consuming them
131
+ @password_shadow_conversion_algorithm = "SALTED-SHA512"
132
+ @current_resource.password(shadow_hash["SALTED-SHA512"].string.unpack('H*').first)
133
+ elsif shadow_hash["SALTED-SHA512-PBKDF2"]
134
+ @password_shadow_conversion_algorithm = "SALTED-SHA512-PBKDF2"
135
+ # Convert the entropy from Base64 encoding to hex before consuming them
136
+ @current_resource.password(shadow_hash["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first)
137
+ @current_resource.iterations(shadow_hash["SALTED-SHA512-PBKDF2"]["iterations"])
138
+ # Convert the salt from Base64 encoding to hex before consuming them
139
+ @current_resource.salt(shadow_hash["SALTED-SHA512-PBKDF2"]["salt"].string.unpack('H*').first)
140
+ else
141
+ raise(Chef::Exceptions::User,"Unknown shadow_hash format: #{shadow_hash.keys.join(' ')}")
142
+ end
143
+ end
144
+
145
+ convert_group_name if @new_resource.gid
146
+ else
147
+ @user_exists = false
148
+ Chef::Log.debug("#{@new_resource} user does not exist")
149
+ end
150
+
151
+ @current_resource
152
+ end
153
+
154
+ #
155
+ # Provider Actions
156
+ #
157
+
158
+ def create_user
159
+ dscl_create_user
160
+ dscl_create_comment
161
+ dscl_set_uid
162
+ dscl_set_gid
163
+ dscl_set_home
164
+ dscl_set_shell
165
+ set_password
40
166
  end
41
167
 
42
- # This is handled in providers/group.rb by Etc.getgrnam()
43
- # def user_exists?(user)
44
- # users = safe_dscl("list /Users")
45
- # !! ( users =~ Regexp.new("\n#{user}\n") )
46
- # end
168
+ def manage_user
169
+ dscl_create_user if diverged?(:username)
170
+ dscl_create_comment if diverged?(:comment)
171
+ dscl_set_uid if diverged?(:uid)
172
+ dscl_set_gid if diverged?(:gid)
173
+ dscl_set_home if diverged?(:home)
174
+ dscl_set_shell if diverged?(:shell)
175
+ set_password if diverged_password?
176
+ end
47
177
 
48
- # get a free UID greater than 200
178
+ #
179
+ # Action Helpers
180
+ #
181
+
182
+ #
183
+ # Create a user using dscl
184
+ #
185
+ def dscl_create_user
186
+ run_dscl("create /Users/#{@new_resource.username}")
187
+ end
188
+
189
+ #
190
+ # Saves the specified Chef user `comment` into RealName attribute
191
+ # of Mac user.
192
+ #
193
+ def dscl_create_comment
194
+ run_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
195
+ end
196
+
197
+ #
198
+ # Sets the user id for the user using dscl.
199
+ # If a `uid` is not specified, it finds the next available one starting
200
+ # from 200 if `system` is set, 500 otherwise.
201
+ #
202
+ def dscl_set_uid
203
+ @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
204
+
205
+ if uid_used?(@new_resource.uid)
206
+ raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
207
+ end
208
+
209
+ run_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
210
+ end
211
+
212
+ #
213
+ # Find the next available uid on the system. starting with 200 if `system` is set,
214
+ # 500 otherwise.
215
+ #
49
216
  def get_free_uid(search_limit=1000)
50
- uid = nil; next_uid_guess = 200
51
- users_uids = safe_dscl("list /Users uid")
52
- while(next_uid_guess < search_limit + 200)
217
+ uid = nil
218
+ base_uid = @new_resource.system ? 200 : 500
219
+ next_uid_guess = base_uid
220
+ users_uids = run_dscl("list /Users uid")
221
+ while(next_uid_guess < search_limit + base_uid)
53
222
  if users_uids =~ Regexp.new("#{Regexp.escape(next_uid_guess.to_s)}\n")
54
223
  next_uid_guess += 1
55
224
  else
@@ -60,22 +229,41 @@ class Chef
60
229
  return uid || raise("uid not found. Exhausted. Searched #{search_limit} times")
61
230
  end
62
231
 
232
+ #
233
+ # Returns true if uid is in use by a different account, false otherwise.
234
+ #
63
235
  def uid_used?(uid)
64
236
  return false unless uid
65
- users_uids = safe_dscl("list /Users uid")
237
+ users_uids = run_dscl("list /Users uid")
66
238
  !! ( users_uids =~ Regexp.new("#{Regexp.escape(uid.to_s)}\n") )
67
239
  end
68
240
 
69
- def set_uid
70
- @new_resource.uid(get_free_uid) if (@new_resource.uid.nil? || @new_resource.uid == '')
71
- if uid_used?(@new_resource.uid)
72
- raise(Chef::Exceptions::RequestedUIDUnavailable, "uid #{@new_resource.uid} is already in use")
241
+ #
242
+ # Sets the group id for the user using dscl. Fails if a group doesn't
243
+ # exist on the system with given group id.
244
+ #
245
+ def dscl_set_gid
246
+ unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
247
+ begin
248
+ possible_gid = run_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
249
+ rescue Chef::Exceptions::DsclCommandFailed => e
250
+ raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
251
+ end
252
+ @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
73
253
  end
74
- safe_dscl("create /Users/#{@new_resource.username} UniqueID #{@new_resource.uid}")
254
+ run_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
75
255
  end
76
256
 
77
- def modify_home
78
- return safe_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory") if (@new_resource.home.nil? || @new_resource.home.empty?)
257
+ #
258
+ # Sets the home directory for the user. If `:manage_home` is set home
259
+ # directory is managed (moved / created) for the user.
260
+ #
261
+ def dscl_set_home
262
+ if @new_resource.home.nil? || @new_resource.home.empty?
263
+ run_dscl("delete /Users/#{@new_resource.username} NFSHomeDirectory")
264
+ return
265
+ end
266
+
79
267
  if @new_resource.supports[:manage_home]
80
268
  validate_home_dir_specification!
81
269
 
@@ -87,199 +275,399 @@ class Chef
87
275
  move_home
88
276
  end
89
277
  end
90
- safe_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
278
+ run_dscl("create /Users/#{@new_resource.username} NFSHomeDirectory '#{@new_resource.home}'")
279
+ end
280
+
281
+ def validate_home_dir_specification!
282
+ unless @new_resource.home =~ /^\//
283
+ raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
284
+ end
91
285
  end
92
286
 
93
- def osx_shadow_hash?(string)
94
- return !! ( string =~ /^[[:xdigit:]]{1240}$/ )
287
+ def current_home_exists?
288
+ ::File.exist?("#{@current_resource.home}")
95
289
  end
96
290
 
97
- def osx_salted_sha1?(string)
98
- return !! ( string =~ /^[[:xdigit:]]{48}$/ )
291
+ def new_home_exists?
292
+ ::File.exist?("#{@new_resource.home}")
99
293
  end
100
294
 
101
- def guid
102
- safe_dscl("read /Users/#{@new_resource.username} GeneratedUID").gsub(/GeneratedUID: /,"").strip
295
+ def ditto_home
296
+ skel = "/System/Library/User Template/English.lproj"
297
+ raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
298
+ shell_out! "ditto '#{skel}' '#{@new_resource.home}'"
299
+ ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
300
+ end
301
+
302
+ def move_home
303
+ Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
304
+
305
+ src = @current_resource.home
306
+ FileUtils.mkdir_p(@new_resource.home)
307
+ files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
308
+ ::FileUtils.mv(files,@new_resource.home, :force => true)
309
+ ::FileUtils.rmdir(src)
310
+ ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
103
311
  end
104
312
 
105
- def shadow_hash_set?
106
- user_data = safe_dscl("read /Users/#{@new_resource.username}")
107
- if user_data =~ /AuthenticationAuthority: / && user_data =~ /ShadowHash/
108
- true
313
+ #
314
+ # Sets the shell for the user using dscl.
315
+ #
316
+ def dscl_set_shell
317
+ if @new_resource.shell || ::File.exists?("#{@new_resource.shell}")
318
+ run_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
109
319
  else
110
- false
320
+ run_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
111
321
  end
112
322
  end
113
323
 
114
- def modify_password
115
- if @new_resource.password
116
- shadow_hash = nil
324
+ #
325
+ # Sets the password for the user based on given password parameters.
326
+ # Chef supports specifying plain-text passwords and password shadow
327
+ # hash data.
328
+ #
329
+ def set_password
330
+ # Return if there is no password to set
331
+ return if @new_resource.password.nil?
332
+
333
+ shadow_info = prepare_password_shadow_info
334
+
335
+ # Shadow info is saved as binary plist. Convert the info to binary plist.
336
+ shadow_info_binary = StringIO.new
337
+ command = Mixlib::ShellOut.new("plutil -convert binary1 -o - -",
338
+ :input => shadow_info.to_plist, :live_stream => shadow_info_binary)
339
+ command.run_command
340
+
341
+ # Replace the shadow info in user's plist
342
+ user_info = read_user_info
343
+ dscl_set(user_info, :shadow_hash, shadow_info_binary)
344
+
345
+ #
346
+ # Before saving the user's plist file we need to wait for dscl to
347
+ # update its caches and flush them to disk. In order to achieve this
348
+ # we need to wait first for our changes to get into the dscl cache
349
+ # and then flush the cache to disk before saving password into the
350
+ # plist file. 3 seconds is the minimum experimental value for dscl
351
+ # cache to be updated. We can get rid of this sleep when we find a
352
+ # trigger to update dscl cache.
353
+ #
354
+ sleep 3
355
+ shell_out("dscacheutil '-flushcache'")
356
+ save_user_info(user_info)
357
+ end
117
358
 
118
- Chef::Log.debug("#{new_resource} updating password")
119
- if osx_shadow_hash?(@new_resource.password)
120
- shadow_hash = @new_resource.password.upcase
359
+ #
360
+ # Prepares the password shadow info based on the platform version.
361
+ #
362
+ def prepare_password_shadow_info
363
+ shadow_info = { }
364
+ entropy = nil
365
+ salt = nil
366
+ iterations = nil
367
+
368
+ if mac_osx_version_10_7?
369
+ hash_value = if salted_sha512?(@new_resource.password)
370
+ @new_resource.password
121
371
  else
122
- if osx_salted_sha1?(@new_resource.password)
123
- salted_sha1 = @new_resource.password.upcase
124
- else
125
- hex_salt = ""
126
- OpenSSL::Random.random_bytes(10).each_byte { |b| hex_salt << b.to_i.to_s(16) }
127
- hex_salt = hex_salt.slice(0...8)
128
- salt = [hex_salt].pack("H*")
129
- sha1 = ::OpenSSL::Digest::SHA1.hexdigest(salt+@new_resource.password)
130
- salted_sha1 = (hex_salt+sha1).upcase
131
- end
132
- shadow_hash = String.new("00000000"*155)
133
- shadow_hash[168] = salted_sha1
372
+ # Create a random 4 byte salt
373
+ salt = OpenSSL::Random.random_bytes(4)
374
+ encoded_password = OpenSSL::Digest::SHA512.hexdigest(salt + @new_resource.password)
375
+ hash_value = salt.unpack('H*').first + encoded_password
134
376
  end
135
377
 
136
- ::File.open("/var/db/shadow/hash/#{guid}",'w',0600) do |output|
137
- output.puts shadow_hash
378
+ shadow_info["SALTED-SHA512"] = StringIO.new
379
+ shadow_info["SALTED-SHA512"].string = convert_to_binary(hash_value)
380
+ shadow_info
381
+ else
382
+ if salted_sha512_pbkdf2?(@new_resource.password)
383
+ entropy = convert_to_binary(@new_resource.password)
384
+ salt = convert_to_binary(@new_resource.salt)
385
+ iterations = @new_resource.iterations
386
+ else
387
+ salt = OpenSSL::Random.random_bytes(32)
388
+ iterations = @new_resource.iterations # Use the default if not specified by the user
389
+
390
+ entropy = OpenSSL::PKCS5::pbkdf2_hmac(
391
+ @new_resource.password,
392
+ salt,
393
+ iterations,
394
+ 128,
395
+ OpenSSL::Digest::SHA512.new
396
+ )
138
397
  end
139
398
 
140
- unless shadow_hash_set?
141
- safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';ShadowHash;'")
399
+ pbkdf_info = { }
400
+ pbkdf_info["entropy"] = StringIO.new
401
+ pbkdf_info["entropy"].string = entropy
402
+ pbkdf_info["salt"] = StringIO.new
403
+ pbkdf_info["salt"].string = salt
404
+ pbkdf_info["iterations"] = iterations
405
+
406
+ shadow_info["SALTED-SHA512-PBKDF2"] = pbkdf_info
407
+ end
408
+
409
+ shadow_info
410
+ end
411
+
412
+ #
413
+ # Removes the user from the system after removing user from his groups
414
+ # and deleting home directory if needed.
415
+ #
416
+ def remove_user
417
+ if @new_resource.supports[:manage_home]
418
+ # Remove home directory
419
+ FileUtils.rm_rf(@current_resource.home)
420
+ end
421
+
422
+ # Remove the user from its groups
423
+ run_dscl("list /Groups").each_line do |group|
424
+ if member_of_group?(group.chomp)
425
+ run_dscl("delete /Groups/#{group.chomp} GroupMembership '#{@new_resource.username}'")
142
426
  end
143
427
  end
428
+
429
+ # Remove user account
430
+ run_dscl("delete /Users/#{@new_resource.username}")
144
431
  end
145
432
 
146
- def load_current_resource
147
- super
148
- raise Chef::Exceptions::User, "Could not find binary /usr/bin/dscl for #{@new_resource}" unless ::File.exists?("/usr/bin/dscl")
433
+ #
434
+ # Locks the user.
435
+ #
436
+ def lock_user
437
+ run_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
149
438
  end
150
439
 
151
- def create_user
152
- dscl_create_user
153
- dscl_create_comment
154
- set_uid
155
- dscl_set_gid
156
- modify_home
157
- dscl_set_shell
158
- modify_password
440
+ #
441
+ # Unlocks the user
442
+ #
443
+ def unlock_user
444
+ auth_string = @authentication_authority.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip
445
+ run_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
159
446
  end
160
447
 
161
- def manage_user
162
- dscl_create_user if diverged?(:username)
163
- dscl_create_comment if diverged?(:comment)
164
- set_uid if diverged?(:uid)
165
- dscl_set_gid if diverged?(:gid)
166
- modify_home if diverged?(:home)
167
- dscl_set_shell if diverged?(:shell)
168
- modify_password if diverged?(:password)
448
+ #
449
+ # Returns true if the user is locked, false otherwise.
450
+ #
451
+ def locked?
452
+ if @authentication_authority
453
+ !!(@authentication_authority =~ /DisabledUser/ )
454
+ else
455
+ false
456
+ end
169
457
  end
170
458
 
171
- def dscl_create_user
172
- safe_dscl("create /Users/#{@new_resource.username}")
459
+ #
460
+ # This is the interface base User provider requires to provide idempotency.
461
+ #
462
+ def check_lock
463
+ return @locked = locked?
173
464
  end
174
465
 
175
- def dscl_create_comment
176
- safe_dscl("create /Users/#{@new_resource.username} RealName '#{@new_resource.comment}'")
466
+ #
467
+ # Helper functions
468
+ #
469
+
470
+ #
471
+ # Returns true if the system state and desired state is different for
472
+ # given attribute.
473
+ #
474
+ def diverged?(parameter)
475
+ parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
177
476
  end
178
477
 
179
- def dscl_set_gid
180
- unless @new_resource.gid && @new_resource.gid.to_s.match(/^\d+$/)
181
- begin
182
- possible_gid = safe_dscl("read /Groups/#{@new_resource.gid} PrimaryGroupID").split(" ").last
183
- rescue Chef::Exceptions::DsclCommandFailed => e
184
- raise Chef::Exceptions::GroupIDNotFound.new("Group not found for #{@new_resource.gid} when creating user #{@new_resource.username}")
185
- end
186
- @new_resource.gid(possible_gid) if possible_gid && possible_gid.match(/^\d+$/)
187
- end
188
- safe_dscl("create /Users/#{@new_resource.username} PrimaryGroupID '#{@new_resource.gid}'")
478
+ def parameter_updated?(parameter)
479
+ not (@new_resource.send(parameter) == @current_resource.send(parameter))
189
480
  end
190
481
 
191
- def dscl_set_shell
192
- if @new_resource.password || ::File.exists?("#{@new_resource.shell}")
193
- safe_dscl("create /Users/#{@new_resource.username} UserShell '#{@new_resource.shell}'")
482
+ #
483
+ # We need a special check function for password since we support both
484
+ # plain text and shadow hash data.
485
+ #
486
+ # Checks if password needs update based on platform version and the
487
+ # type of the password specified.
488
+ #
489
+ def diverged_password?
490
+ return false if @new_resource.password.nil?
491
+
492
+ # Dscl provider supports both plain text passwords and shadow hashes.
493
+ if mac_osx_version_10_7?
494
+ if salted_sha512?(@new_resource.password)
495
+ diverged?(:password)
496
+ else
497
+ !salted_sha512_password_match?
498
+ end
194
499
  else
195
- safe_dscl("create /Users/#{@new_resource.username} UserShell '/usr/bin/false'")
500
+ # When a system is upgraded to a version 10.7+ shadow hashes of the users
501
+ # will be updated when the user logs in. So it's possible that we will have
502
+ # SALTED-SHA512 password in the current_resource. In that case we will force
503
+ # password to be updated.
504
+ return true if salted_sha512?(@current_resource.password)
505
+
506
+ if salted_sha512_pbkdf2?(@new_resource.password)
507
+ diverged?(:password) || diverged?(:salt) || diverged?(:iterations)
508
+ else
509
+ !salted_sha512_pbkdf2_password_match?
510
+ end
196
511
  end
197
512
  end
198
513
 
199
- def remove_user
200
- if @new_resource.supports[:manage_home]
201
- user_info = safe_dscl("read /Users/#{@new_resource.username}")
202
- if nfs_home_match = user_info.match(NFS_HOME_DIRECTORY)
203
- #nfs_home = safe_dscl("read /Users/#{@new_resource.username} NFSHomeDirectory")
204
- #nfs_home.gsub!(/NFSHomeDirectory: /,"").gsub!(/\n$/,"")
205
- nfs_home = nfs_home_match[1]
206
- FileUtils.rm_rf(nfs_home)
207
- end
208
- end
209
- # remove the user from its groups
210
- groups = []
211
- Etc.group do |group|
212
- groups << group.name if group.mem.include?(@new_resource.username)
213
- end
214
- groups.each do |group_name|
215
- safe_dscl("delete /Groups/#{group_name} GroupMembership '#{@new_resource.username}'")
514
+ #
515
+ # Returns true if user is member of the specified group, false otherwise.
516
+ #
517
+ def member_of_group?(group_name)
518
+ membership_info = ""
519
+ begin
520
+ membership_info = run_dscl("read /Groups/#{group_name}")
521
+ rescue Chef::Exceptions::DsclCommandFailed
522
+ # Raised if the group doesn't contain any members
216
523
  end
217
- # remove user account
218
- safe_dscl("delete /Users/#{@new_resource.username}")
524
+ # Output is something like:
525
+ # GroupMembership: root admin etc
526
+ members = membership_info.split(" ")
527
+ members.shift # Get rid of GroupMembership: string
528
+ members.include?(@new_resource.username)
219
529
  end
220
530
 
221
- def locked?
222
- user_info = safe_dscl("read /Users/#{@new_resource.username}")
223
- if auth_authority_md = AUTHENTICATION_AUTHORITY.match(user_info)
224
- !!(auth_authority_md[1] =~ /DisabledUser/ )
225
- else
226
- false
531
+ #
532
+ # DSCL Helper functions
533
+ #
534
+
535
+ # A simple map of Chef's terms to DSCL's terms.
536
+ DSCL_PROPERTY_MAP = {
537
+ :uid => "generateduid",
538
+ :gid => "gid",
539
+ :home => "home",
540
+ :shell => "shell",
541
+ :comment => "realname",
542
+ :password => "passwd",
543
+ :auth_authority => "authentication_authority",
544
+ :shadow_hash => "ShadowHashData"
545
+ }.freeze
546
+
547
+ # Directory where the user plist files are stored for versions 10.7 and above
548
+ USER_PLIST_DIRECTORY = "/var/db/dslocal/nodes/Default/users".freeze
549
+
550
+ #
551
+ # Reads the user plist and returns a hash keyed with DSCL properties specified
552
+ # in DSCL_PROPERTY_MAP. Return nil if the user is not found.
553
+ #
554
+ def read_user_info
555
+ user_info = nil
556
+
557
+ begin
558
+ user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
559
+ user_plist_info = run_plutil("convert xml1 -o - #{user_plist_file}")
560
+ user_info = Plist::parse_xml(user_plist_info)
561
+ rescue Chef::Exceptions::PlistUtilCommandFailed
227
562
  end
563
+
564
+ user_info
228
565
  end
229
566
 
230
- def check_lock
231
- return @locked = locked?
567
+ #
568
+ # Saves the given hash keyed with DSCL properties specified
569
+ # in DSCL_PROPERTY_MAP to the disk.
570
+ #
571
+ def save_user_info(user_info)
572
+ user_plist_file = "#{USER_PLIST_DIRECTORY}/#{@new_resource.username}.plist"
573
+ Plist::Emit.save_plist(user_info, user_plist_file)
574
+ run_plutil("convert binary1 #{user_plist_file}")
232
575
  end
233
576
 
234
- def lock_user
235
- safe_dscl("append /Users/#{@new_resource.username} AuthenticationAuthority ';DisabledUser;'")
577
+ #
578
+ # Sets a value in user information hash using Chef attributes as keys.
579
+ #
580
+ def dscl_set(user_hash, key, value)
581
+ raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key)
582
+ user_hash[DSCL_PROPERTY_MAP[key]] = [ value ]
583
+ user_hash
236
584
  end
237
585
 
238
- def unlock_user
239
- auth_info = safe_dscl("read /Users/#{@new_resource.username} AuthenticationAuthority")
240
- auth_string = auth_info.gsub(/AuthenticationAuthority: /,"").gsub(/;DisabledUser;/,"").strip#.gsub!(/[; ]*$/,"")
241
- safe_dscl("create /Users/#{@new_resource.username} AuthenticationAuthority '#{auth_string}'")
586
+ #
587
+ # Gets a value from user information hash using Chef attributes as keys.
588
+ #
589
+ def dscl_get(user_hash, key)
590
+ raise "Unknown dscl key #{key}" unless DSCL_PROPERTY_MAP.keys.include?(key)
591
+ # DSCL values are set as arrays
592
+ value = user_hash[DSCL_PROPERTY_MAP[key]]
593
+ value.nil? ? value : value.first
242
594
  end
243
595
 
244
- def validate_home_dir_specification!
245
- unless @new_resource.home =~ /^\//
246
- raise(Chef::Exceptions::InvalidHomeDirectory,"invalid path spec for User: '#{@new_resource.username}', home directory: '#{@new_resource.home}'")
247
- end
596
+ #
597
+ # System Helpets
598
+ #
599
+
600
+ def mac_osx_version
601
+ # This provider will only be invoked on node[:platform] == "mac_os_x"
602
+ # We do not check or assert that here.
603
+ node[:platform_version]
248
604
  end
249
605
 
250
- def current_home_exists?
251
- ::File.exist?("#{@current_resource.home}")
606
+ def mac_osx_version_10_7?
607
+ mac_osx_version.start_with?("10.7.")
252
608
  end
253
609
 
254
- def new_home_exists?
255
- ::File.exist?("#{@new_resource.home}")
610
+ def mac_osx_version_less_than_10_7?
611
+ versions = mac_osx_version.split(".")
612
+ # Make integer comparison in order not to report 10.10 less than 10.7
613
+ (versions[0].to_i <= 10 && versions[1].to_i < 7)
256
614
  end
257
615
 
258
- def ditto_home
259
- skel = "/System/Library/User Template/English.lproj"
260
- raise(Chef::Exceptions::User,"can't find skel at: #{skel}") unless ::File.exists?(skel)
261
- shell_out! "ditto '#{skel}' '#{@new_resource.home}'"
262
- ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
616
+ def mac_osx_version_greater_than_10_7?
617
+ versions = mac_osx_version.split(".")
618
+ # Make integer comparison in order not to report 10.10 less than 10.7
619
+ (versions[0].to_i >= 10 && versions[1].to_i > 7)
263
620
  end
264
621
 
265
- def move_home
266
- Chef::Log.debug("#{@new_resource} moving #{self} home from #{@current_resource.home} to #{@new_resource.home}")
622
+ def run_dscl(*args)
623
+ result = shell_out("dscl . -#{args.join(' ')}")
624
+ return "" if ( args.first =~ /^delete/ ) && ( result.exitstatus != 0 )
625
+ raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") unless result.exitstatus == 0
626
+ raise(Chef::Exceptions::DsclCommandFailed,"dscl error: #{result.inspect}") if result.stdout =~ /No such key: /
627
+ result.stdout
628
+ end
267
629
 
268
- src = @current_resource.home
269
- FileUtils.mkdir_p(@new_resource.home)
270
- files = ::Dir.glob("#{src}/*", ::File::FNM_DOTMATCH) - ["#{src}/.","#{src}/.."]
271
- ::FileUtils.mv(files,@new_resource.home, :force => true)
272
- ::FileUtils.rmdir(src)
273
- ::FileUtils.chown_R(@new_resource.username,@new_resource.gid.to_s,@new_resource.home)
630
+ def run_plutil(*args)
631
+ result = shell_out("plutil -#{args.join(' ')}")
632
+ raise(Chef::Exceptions::PlistUtilCommandFailed,"plutil error: #{result.inspect}") unless result.exitstatus == 0
633
+ result.stdout
274
634
  end
275
635
 
276
- def diverged?(parameter)
277
- parameter_updated?(parameter) && (not @new_resource.send(parameter).nil?)
636
+ def convert_binary_plist_to_xml(binary_plist_string)
637
+ Mixlib::ShellOut.new("plutil -convert xml1 -o - -", :input => binary_plist_string).run_command.stdout
278
638
  end
279
639
 
280
- def parameter_updated?(parameter)
281
- not (@new_resource.send(parameter) == @current_resource.send(parameter))
640
+ def convert_to_binary(string)
641
+ string.unpack('a2'*(string.size/2)).collect { |i| i.hex.chr }.join
642
+ end
643
+
644
+ def salted_sha512?(string)
645
+ !!(string =~ /^[[:xdigit:]]{136}$/)
646
+ end
647
+
648
+ def salted_sha512_password_match?
649
+ # Salt is included in the first 4 bytes of shadow data
650
+ salt = @current_resource.password.slice(0,8)
651
+ shadow = OpenSSL::Digest::SHA512.hexdigest(convert_to_binary(salt) + @new_resource.password)
652
+ @current_resource.password == salt + shadow
282
653
  end
654
+
655
+ def salted_sha512_pbkdf2?(string)
656
+ !!(string =~ /^[[:xdigit:]]{256}$/)
657
+ end
658
+
659
+ def salted_sha512_pbkdf2_password_match?
660
+ salt = convert_to_binary(@current_resource.salt)
661
+
662
+ OpenSSL::PKCS5::pbkdf2_hmac(
663
+ @new_resource.password,
664
+ salt,
665
+ @current_resource.iterations,
666
+ 128,
667
+ OpenSSL::Digest::SHA512.new
668
+ ).unpack('H*').first == @current_resource.password
669
+ end
670
+
283
671
  end
284
672
  end
285
673
  end