chef 11.18.12-x86-mingw32 → 12.0.0.alpha.0-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
Files changed (307) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +10 -0
  3. data/README.md +1 -1
  4. data/distro/common/html/_sources/index.txt +5 -2
  5. data/distro/common/html/_sources/knife_serve.txt +19 -0
  6. data/distro/common/html/_sources/knife_ssl_check.txt +2 -2
  7. data/distro/common/html/_sources/knife_ssl_fetch.txt +2 -2
  8. data/distro/common/html/_static/basic.css +1 -1
  9. data/distro/common/html/_static/doctools.js +1 -1
  10. data/distro/common/html/_static/searchtools.js +1 -1
  11. data/distro/common/html/_static/websupport.js +1 -1
  12. data/distro/common/html/ctl_chef_client.html +19 -9
  13. data/distro/common/html/ctl_chef_server.html +7 -1
  14. data/distro/common/html/ctl_chef_shell.html +3 -4
  15. data/distro/common/html/ctl_chef_solo.html +12 -7
  16. data/distro/common/html/index.html +19 -12
  17. data/distro/common/html/knife.html +1 -2
  18. data/distro/common/html/knife_bootstrap.html +16 -5
  19. data/distro/common/html/knife_client.html +8 -5
  20. data/distro/common/html/knife_common_options.html +11 -12
  21. data/distro/common/html/knife_configure.html +2 -3
  22. data/distro/common/html/knife_cookbook.html +16 -17
  23. data/distro/common/html/knife_cookbook_site.html +19 -18
  24. data/distro/common/html/knife_data_bag.html +6 -7
  25. data/distro/common/html/knife_delete.html +2 -3
  26. data/distro/common/html/knife_deps.html +2 -3
  27. data/distro/common/html/knife_diff.html +3 -4
  28. data/distro/common/html/knife_download.html +6 -7
  29. data/distro/common/html/knife_edit.html +0 -1
  30. data/distro/common/html/knife_environment.html +3 -4
  31. data/distro/common/html/knife_exec.html +0 -1
  32. data/distro/common/html/knife_index_rebuild.html +0 -1
  33. data/distro/common/html/knife_list.html +5 -6
  34. data/distro/common/html/knife_node.html +13 -6
  35. data/distro/common/html/knife_raw.html +0 -1
  36. data/distro/common/html/knife_recipe_list.html +0 -1
  37. data/distro/common/html/knife_role.html +1 -2
  38. data/distro/common/html/knife_search.html +2 -3
  39. data/distro/common/html/knife_serve.html +79 -0
  40. data/distro/common/html/knife_show.html +1 -2
  41. data/distro/common/html/knife_ssh.html +2 -3
  42. data/distro/common/html/knife_ssl_check.html +12 -9
  43. data/distro/common/html/knife_ssl_fetch.html +9 -10
  44. data/distro/common/html/knife_status.html +2 -3
  45. data/distro/common/html/knife_tag.html +0 -1
  46. data/distro/common/html/knife_upload.html +3 -4
  47. data/distro/common/html/knife_user.html +2 -3
  48. data/distro/common/html/knife_using.html +0 -1
  49. data/distro/common/html/knife_xargs.html +3 -4
  50. data/distro/common/html/search.html +0 -1
  51. data/distro/common/html/searchindex.js +1 -1
  52. data/lib/chef/api_client.rb +1 -1
  53. data/lib/chef/application.rb +24 -67
  54. data/lib/chef/application/client.rb +1 -1
  55. data/lib/chef/application/knife.rb +1 -1
  56. data/lib/chef/chef_fs/chef_fs_data_store.rb +2 -3
  57. data/lib/chef/chef_fs/command_line.rb +2 -3
  58. data/lib/chef/chef_fs/data_handler/group_data_handler.rb +1 -5
  59. data/lib/chef/chef_fs/file_system/acl_entry.rb +1 -2
  60. data/lib/chef/chef_fs/file_system/chef_repository_file_system_entry.rb +1 -2
  61. data/lib/chef/chef_fs/file_system/cookbooks_dir.rb +2 -2
  62. data/lib/chef/chef_fs/file_system/rest_list_dir.rb +2 -3
  63. data/lib/chef/chef_fs/file_system/rest_list_entry.rb +4 -5
  64. data/lib/chef/config.rb +26 -2
  65. data/lib/chef/config_fetcher.rb +1 -1
  66. data/lib/chef/cookbook/cookbook_version_loader.rb +4 -4
  67. data/lib/chef/cookbook/metadata.rb +1 -1
  68. data/lib/chef/cookbook/synchronizer.rb +50 -8
  69. data/lib/chef/cookbook_uploader.rb +9 -23
  70. data/lib/chef/cookbook_version.rb +3 -2
  71. data/lib/chef/data_bag.rb +1 -1
  72. data/lib/chef/data_bag_item.rb +1 -1
  73. data/lib/chef/dsl/recipe.rb +1 -14
  74. data/lib/chef/encrypted_data_bag_item/decryptor.rb +3 -3
  75. data/lib/chef/environment.rb +1 -1
  76. data/lib/chef/exceptions.rb +2 -24
  77. data/lib/chef/file_content_management/tempfile.rb +8 -1
  78. data/lib/chef/formatters/base.rb +0 -7
  79. data/lib/chef/http.rb +12 -19
  80. data/lib/chef/http/json_input.rb +12 -1
  81. data/lib/chef/json_compat.rb +45 -64
  82. data/lib/chef/knife.rb +2 -5
  83. data/lib/chef/knife/bootstrap.rb +2 -2
  84. data/lib/chef/knife/bootstrap/archlinux-gems.erb +2 -2
  85. data/lib/chef/knife/bootstrap/centos5-gems.erb +2 -2
  86. data/lib/chef/knife/bootstrap/chef-aix.erb +2 -2
  87. data/lib/chef/knife/bootstrap/chef-full.erb +2 -2
  88. data/lib/chef/knife/bootstrap/fedora13-gems.erb +2 -2
  89. data/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +2 -2
  90. data/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +2 -2
  91. data/lib/chef/knife/bootstrap/ubuntu12.04-gems.erb +2 -2
  92. data/lib/chef/knife/cookbook_site_download.rb +1 -1
  93. data/lib/chef/knife/cookbook_site_install.rb +10 -34
  94. data/lib/chef/knife/cookbook_site_list.rb +1 -1
  95. data/lib/chef/knife/cookbook_site_search.rb +1 -1
  96. data/lib/chef/knife/cookbook_site_share.rb +3 -3
  97. data/lib/chef/knife/cookbook_site_show.rb +3 -3
  98. data/lib/chef/knife/cookbook_site_unshare.rb +1 -1
  99. data/lib/chef/knife/cookbook_upload.rb +1 -1
  100. data/lib/chef/knife/core/subcommand_loader.rb +0 -24
  101. data/lib/chef/knife/core/ui.rb +8 -7
  102. data/lib/chef/knife/deps.rb +2 -3
  103. data/lib/chef/knife/serve.rb +2 -1
  104. data/lib/chef/local_mode.rb +105 -0
  105. data/lib/chef/mixin/file_class.rb +1 -4
  106. data/lib/chef/mixin/shell_out.rb +15 -0
  107. data/lib/chef/mixin/windows_architecture_helper.rb +0 -16
  108. data/lib/chef/node.rb +1 -1
  109. data/lib/chef/platform/query_helpers.rb +1 -5
  110. data/lib/chef/policy_builder/expand_node_object.rb +3 -0
  111. data/lib/chef/provider.rb +2 -0
  112. data/lib/chef/provider/cookbook_file.rb +0 -1
  113. data/lib/chef/provider/deploy.rb +0 -1
  114. data/lib/chef/provider/deploy/revision.rb +1 -1
  115. data/lib/chef/provider/env.rb +10 -25
  116. data/lib/chef/provider/env/windows.rb +23 -10
  117. data/lib/chef/provider/execute.rb +0 -3
  118. data/lib/chef/provider/file.rb +0 -3
  119. data/lib/chef/provider/git.rb +0 -6
  120. data/lib/chef/provider/group/dscl.rb +9 -27
  121. data/lib/chef/provider/group/gpasswd.rb +0 -3
  122. data/lib/chef/provider/group/groupmod.rb +0 -4
  123. data/lib/chef/provider/group/suse.rb +0 -3
  124. data/lib/chef/provider/group/usermod.rb +0 -3
  125. data/lib/chef/provider/link.rb +22 -5
  126. data/lib/chef/provider/log.rb +15 -4
  127. data/lib/chef/provider/mdadm.rb +0 -3
  128. data/lib/chef/provider/mount/mount.rb +0 -2
  129. data/lib/chef/provider/mount/solaris.rb +0 -2
  130. data/lib/chef/provider/package.rb +0 -1
  131. data/lib/chef/provider/package/apt.rb +0 -3
  132. data/lib/chef/provider/package/dpkg.rb +0 -1
  133. data/lib/chef/provider/package/easy_install.rb +0 -4
  134. data/lib/chef/provider/package/freebsd/base.rb +0 -3
  135. data/lib/chef/provider/package/freebsd/pkgng.rb +0 -2
  136. data/lib/chef/provider/package/freebsd/port.rb +0 -2
  137. data/lib/chef/provider/package/ips.rb +0 -3
  138. data/lib/chef/provider/package/paludis.rb +0 -5
  139. data/lib/chef/provider/package/portage.rb +0 -2
  140. data/lib/chef/provider/package/rpm.rb +2 -4
  141. data/lib/chef/provider/package/rubygems.rb +0 -4
  142. data/lib/chef/provider/package/smartos.rb +0 -3
  143. data/lib/chef/provider/package/windows/msi.rb +0 -2
  144. data/lib/chef/provider/package/yum.rb +0 -4
  145. data/lib/chef/provider/package/zypper.rb +0 -3
  146. data/lib/chef/provider/registry_key.rb +0 -2
  147. data/lib/chef/provider/remote_directory.rb +0 -1
  148. data/lib/chef/provider/remote_file.rb +0 -1
  149. data/lib/chef/provider/remote_file/cache_control_data.rb +1 -3
  150. data/lib/chef/provider/remote_file/content.rb +0 -1
  151. data/lib/chef/provider/remote_file/fetcher.rb +0 -2
  152. data/lib/chef/provider/remote_file/ftp.rb +0 -1
  153. data/lib/chef/provider/resource_update.rb +0 -3
  154. data/lib/chef/provider/service/freebsd.rb +0 -3
  155. data/lib/chef/provider/service/init.rb +0 -3
  156. data/lib/chef/provider/service/macosx.rb +0 -1
  157. data/lib/chef/provider/service/redhat.rb +0 -2
  158. data/lib/chef/provider/service/simple.rb +0 -3
  159. data/lib/chef/provider/service/solaris.rb +0 -3
  160. data/lib/chef/provider/service/systemd.rb +15 -14
  161. data/lib/chef/provider/service/windows.rb +0 -3
  162. data/lib/chef/provider/subversion.rb +0 -2
  163. data/lib/chef/provider/template.rb +0 -2
  164. data/lib/chef/provider/template/content.rb +0 -1
  165. data/lib/chef/provider/user/dscl.rb +156 -549
  166. data/lib/chef/provider/user/solaris.rb +0 -1
  167. data/lib/chef/provider/user/useradd.rb +0 -3
  168. data/lib/chef/provider/whyrun_safe_ruby_block.rb +1 -1
  169. data/lib/chef/providers.rb +0 -1
  170. data/lib/chef/resource.rb +1 -4
  171. data/lib/chef/resource/freebsd_package.rb +2 -10
  172. data/lib/chef/resource/lwrp_base.rb +1 -12
  173. data/lib/chef/resource/user.rb +0 -18
  174. data/lib/chef/resource_collection.rb +1 -1
  175. data/lib/chef/resource_reporter.rb +10 -10
  176. data/lib/chef/resources.rb +0 -1
  177. data/lib/chef/role.rb +3 -3
  178. data/lib/chef/run_list.rb +1 -1
  179. data/lib/chef/tasks/chef_repo.rake +131 -264
  180. data/lib/chef/user.rb +1 -1
  181. data/lib/chef/util/path_helper.rb +2 -2
  182. data/lib/chef/version.rb +9 -1
  183. data/lib/chef/win32/api/system.rb +0 -9
  184. data/spec/data/bootstrap/test-hints.erb +1 -1
  185. data/spec/data/bootstrap/test.erb +1 -1
  186. data/spec/functional/dsl/reboot_pending_spec.rb +53 -58
  187. data/spec/functional/knife/cookbook_delete_spec.rb +3 -3
  188. data/spec/functional/knife/exec_spec.rb +1 -1
  189. data/spec/functional/mixin/shell_out_spec.rb +48 -0
  190. data/spec/functional/resource/base.rb +0 -10
  191. data/spec/functional/resource/group_spec.rb +1 -5
  192. data/spec/functional/resource/link_spec.rb +8 -0
  193. data/spec/functional/resource/{user/useradd_spec.rb → user_spec.rb} +1 -1
  194. data/spec/integration/knife/chef_fs_data_store_spec.rb +3 -3
  195. data/spec/integration/knife/chef_repo_path_spec.rb +1 -6
  196. data/spec/integration/knife/chef_repository_file_system_spec.rb +1 -1
  197. data/spec/integration/knife/chefignore_spec.rb +1 -1
  198. data/spec/integration/knife/common_options_spec.rb +50 -3
  199. data/spec/integration/knife/cookbook_api_ipv6_spec.rb +1 -1
  200. data/spec/integration/knife/delete_spec.rb +1 -1
  201. data/spec/integration/knife/deps_spec.rb +1 -1
  202. data/spec/integration/knife/diff_spec.rb +3 -3
  203. data/spec/integration/knife/download_spec.rb +3 -3
  204. data/spec/integration/knife/list_spec.rb +1 -1
  205. data/spec/integration/knife/raw_spec.rb +1 -11
  206. data/spec/integration/knife/redirection_spec.rb +1 -1
  207. data/spec/integration/knife/serve_spec.rb +2 -2
  208. data/spec/integration/knife/show_spec.rb +1 -1
  209. data/spec/integration/knife/upload_spec.rb +9 -9
  210. data/spec/spec_helper.rb +0 -9
  211. data/spec/support/pedant/pedant_config.rb +2 -1
  212. data/spec/support/pedant/run_pedant.rb +2 -1
  213. data/spec/support/platform_helpers.rb +5 -24
  214. data/spec/support/shared/integration/integration_helper.rb +2 -1
  215. data/spec/support/shared/matchers.rb +17 -0
  216. data/spec/tiny_server.rb +1 -2
  217. data/spec/unit/api_client_spec.rb +3 -3
  218. data/spec/unit/application_spec.rb +9 -32
  219. data/spec/unit/config_fetcher_spec.rb +1 -1
  220. data/spec/unit/cookbook/metadata_spec.rb +3 -7
  221. data/spec/unit/cookbook/synchronizer_spec.rb +441 -226
  222. data/spec/unit/cookbook_loader_spec.rb +1 -1
  223. data/spec/unit/cookbook_uploader_spec.rb +160 -0
  224. data/spec/unit/cookbook_version_spec.rb +0 -4
  225. data/spec/unit/data_bag_item_spec.rb +1 -5
  226. data/spec/unit/data_bag_spec.rb +1 -5
  227. data/spec/unit/deprecation_spec.rb +1 -1
  228. data/spec/unit/dsl/recipe_spec.rb +12 -0
  229. data/spec/unit/encrypted_data_bag_item_spec.rb +7 -14
  230. data/spec/unit/environment_spec.rb +3 -7
  231. data/spec/unit/exceptions_spec.rb +0 -6
  232. data/spec/unit/http/json_input_spec.rb +128 -0
  233. data/spec/unit/json_compat_spec.rb +17 -58
  234. data/spec/unit/knife/client_create_spec.rb +3 -3
  235. data/spec/unit/knife/configure_client_spec.rb +6 -5
  236. data/spec/unit/knife/cookbook_delete_spec.rb +1 -1
  237. data/spec/unit/knife/cookbook_download_spec.rb +5 -5
  238. data/spec/unit/knife/cookbook_metadata_from_file_spec.rb +1 -0
  239. data/spec/unit/knife/cookbook_metadata_spec.rb +1 -1
  240. data/spec/unit/knife/cookbook_site_download_spec.rb +10 -11
  241. data/spec/unit/knife/cookbook_site_install_spec.rb +116 -161
  242. data/spec/unit/knife/cookbook_site_share_spec.rb +8 -8
  243. data/spec/unit/knife/cookbook_upload_spec.rb +3 -2
  244. data/spec/unit/knife/core/bootstrap_context_spec.rb +3 -3
  245. data/spec/unit/knife/core/subcommand_loader_spec.rb +1 -66
  246. data/spec/unit/knife/data_bag_from_file_spec.rb +2 -1
  247. data/spec/unit/knife/tag_create_spec.rb +3 -3
  248. data/spec/unit/knife/tag_delete_spec.rb +3 -3
  249. data/spec/unit/knife/user_create_spec.rb +1 -1
  250. data/spec/unit/knife_spec.rb +14 -14
  251. data/spec/unit/lwrp_spec.rb +1 -21
  252. data/spec/unit/mixin/shell_out_spec.rb +92 -0
  253. data/spec/unit/node_spec.rb +0 -4
  254. data/spec/unit/platform/query_helpers_spec.rb +0 -23
  255. data/spec/unit/provider/env/windows_spec.rb +34 -70
  256. data/spec/unit/provider/env_spec.rb +11 -76
  257. data/spec/unit/provider/group/dscl_spec.rb +1 -38
  258. data/spec/unit/provider/log_spec.rb +18 -0
  259. data/spec/unit/provider/package/rpm_spec.rb +0 -12
  260. data/spec/unit/provider/remote_file/cache_control_data_spec.rb +1 -1
  261. data/spec/unit/provider/service/systemd_service_spec.rb +44 -27
  262. data/spec/unit/provider/user/dscl_spec.rb +264 -660
  263. data/spec/unit/provider/user/useradd_spec.rb +0 -1
  264. data/spec/unit/provider/whyrun_safe_ruby_block_spec.rb +2 -2
  265. data/spec/unit/provider_spec.rb +12 -0
  266. data/spec/unit/recipe_spec.rb +0 -41
  267. data/spec/unit/resource_collection_spec.rb +1 -5
  268. data/spec/unit/resource_reporter_spec.rb +3 -51
  269. data/spec/unit/resource_spec.rb +3 -14
  270. data/spec/unit/rest_spec.rb +1 -4
  271. data/spec/unit/role_spec.rb +0 -10
  272. data/spec/unit/run_list_spec.rb +1 -5
  273. data/spec/unit/user_spec.rb +1 -5
  274. metadata +20 -100
  275. data/lib/chef/mixin/windows_env_helper.rb +0 -56
  276. data/lib/chef/provider/dsc_script.rb +0 -175
  277. data/lib/chef/resource/dsc_script.rb +0 -126
  278. data/lib/chef/streaming_cookbook_uploader.rb +0 -205
  279. data/lib/chef/util/dsc/configuration_generator.rb +0 -115
  280. data/lib/chef/util/dsc/lcm_output_parser.rb +0 -133
  281. data/lib/chef/util/dsc/local_configuration_manager.rb +0 -141
  282. data/lib/chef/util/dsc/resource_info.rb +0 -26
  283. data/lib/chef/util/powershell/cmdlet.rb +0 -136
  284. data/lib/chef/util/powershell/cmdlet_result.rb +0 -46
  285. data/spec/data/mac_users/10.7-8.plist.xml +0 -559
  286. data/spec/data/mac_users/10.7-8.shadow.xml +0 -11
  287. data/spec/data/mac_users/10.7.plist.xml +0 -559
  288. data/spec/data/mac_users/10.7.shadow.xml +0 -11
  289. data/spec/data/mac_users/10.8.plist.xml +0 -559
  290. data/spec/data/mac_users/10.8.shadow.xml +0 -21
  291. data/spec/data/mac_users/10.9.plist.xml +0 -560
  292. data/spec/data/mac_users/10.9.shadow.xml +0 -21
  293. data/spec/functional/provider/whyrun_safe_ruby_block_spec.rb +0 -51
  294. data/spec/functional/resource/dsc_script_spec.rb +0 -382
  295. data/spec/functional/resource/env_spec.rb +0 -182
  296. data/spec/functional/resource/user/dscl_spec.rb +0 -199
  297. data/spec/functional/util/powershell/cmdlet_spec.rb +0 -113
  298. data/spec/support/lib/chef/resource/zen_follower.rb +0 -46
  299. data/spec/support/shared/shared_examples.rb +0 -10
  300. data/spec/unit/chef_fs/data_handler/group_handler_spec.rb +0 -63
  301. data/spec/unit/formatters/base_spec.rb +0 -48
  302. data/spec/unit/provider/dsc_script_spec.rb +0 -174
  303. data/spec/unit/resource/dsc_script_spec.rb +0 -98
  304. data/spec/unit/util/dsc/configuration_generator_spec.rb +0 -171
  305. data/spec/unit/util/dsc/lcm_output_parser_spec.rb +0 -169
  306. data/spec/unit/util/dsc/local_configuration_manager_spec.rb +0 -139
  307. data/spec/unit/util/powershell/cmdlet_spec.rb +0 -106
@@ -20,226 +20,142 @@ ShellCmdResult = Struct.new(:stdout, :stderr, :exitstatus)
20
20
 
21
21
  require 'spec_helper'
22
22
  require 'ostruct'
23
- require 'mixlib/shellout'
24
23
 
25
24
  describe Chef::Provider::User::Dscl do
26
- let(:node) {
27
- node = Chef::Node.new
28
- node.stub(:[]).with(:platform_version).and_return(mac_version)
29
- node.stub(:[]).with(:platform).and_return("mac_os_x")
30
- node
31
- }
32
-
33
- let(:events) {
34
- Chef::EventDispatch::Dispatcher.new
35
- }
36
-
37
- let(:run_context) {
38
- Chef::RunContext.new(node, {}, events)
39
- }
40
-
41
- let(:new_resource) {
42
- r = Chef::Resource::User.new("toor")
43
- r.password(password)
44
- r.salt(salt)
45
- r.iterations(iterations)
46
- r
47
- }
48
-
49
- let(:provider) {
50
- Chef::Provider::User::Dscl.new(new_resource, run_context)
51
- }
52
-
53
- let(:mac_version) {
54
- "10.9.1"
55
- }
56
-
57
- let(:password) { nil }
58
- let(:salt) { nil }
59
- let(:iterations) { nil }
60
-
61
- let(:salted_sha512_password) {
62
- "0f543f021c63255e64e121a3585601b8ecfedf6d2\
63
- 705ddac69e682a33db5dbcdb9b56a2520bc8fff63a\
64
- 2ba6b7984c0737ff0b7949455071581f7affcd536d\
65
- 402b6cdb097"
66
- }
67
-
68
- let(:salted_sha512_pbkdf2_password) {
69
- "c734b6e4787c3727bb35e29fdd92b97c\
70
- 1de12df509577a045728255ec7c6c5f5\
71
- c18efa05ed02b682ffa7ebc05119900e\
72
- b1d4880833aa7a190afc13e2bf0936b8\
73
- 20123e8c98f0f9bcac2a629d9163caac\
74
- 9464a8c234f3919082400b4f939bb77b\
75
- c5adbbac718b7eb99463a7b679571e0f\
76
- 1c9fef2ef08d0b9e9c2bcf644eed2ffc"
77
- }
78
-
79
- let(:salted_sha512_pbkdf2_salt) {
80
- "2d942d8364a9ccf2b8e5cb7ed1ff58f78\
81
- e29dbfee7f9db58859144d061fd0058"
82
- }
83
-
84
- let(:salted_sha512_pbkdf2_iterations) {
85
- 25000
86
- }
87
-
88
- let(:vagrant_sha_512) {
89
- "6f75d7190441facc34291ebbea1fc756b242d4f\
90
- e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\
91
- ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30"
92
- }
93
-
94
- let(:vagrant_sha_512_pbkdf2) {
95
- "12601a90db17cbf\
96
- 8ba4808e6382fb0d3b9d8a6c1a190477bf680ab21afb\
97
- 6065467136e55cc208a6f74156e3daf20fb13369ef4b\
98
- 7bafa047d80359fb46a48a4adccd548ebb33851b093\
99
- 47cca84341a7f93a27147343f89fb843fb46c0017d2\
100
- 64afa4976baacf941b915bd1ec1ca24c30b3e759e02\
101
- 403e02f59fe7ff5938a7636c"
102
- }
103
-
104
- let(:vagrant_sha_512_pbkdf2_salt) {
105
- "ee954be472fdc60ddf89484781433993625f006af6ec810c08f49a7e413946a1"
106
- }
107
-
108
- let(:vagrant_sha_512_pbkdf2_iterations) {
109
- 34482
110
- }
25
+ before do
26
+ @node = Chef::Node.new
27
+ @events = Chef::EventDispatch::Dispatcher.new
28
+ @run_context = Chef::RunContext.new(@node, {}, @events)
29
+ @new_resource = Chef::Resource::User.new("toor")
30
+ @provider = Chef::Provider::User::Dscl.new(@new_resource, @run_context)
31
+ end
111
32
 
112
33
  describe "when shelling out to dscl" do
113
34
  it "should run dscl with the supplied cmd /Path args" do
114
35
  shell_return = ShellCmdResult.new('stdout', 'err', 0)
115
- provider.should_receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return)
116
- provider.run_dscl("cmd /Path args").should == 'stdout'
36
+ @provider.should_receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return)
37
+ @provider.safe_dscl("cmd /Path args").should == 'stdout'
117
38
  end
118
39
 
119
40
  it "returns an empty string from delete commands" do
120
41
  shell_return = ShellCmdResult.new('out', 'err', 23)
121
- provider.should_receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return)
122
- provider.run_dscl("delete /Path args").should == ""
42
+ @provider.should_receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return)
43
+ @provider.safe_dscl("delete /Path args").should == ""
123
44
  end
124
45
 
125
46
  it "should raise an exception for any other command" do
126
47
  shell_return = ShellCmdResult.new('out', 'err', 23)
127
- provider.should_receive(:shell_out).with('dscl . -cmd /Path arguments').and_return(shell_return)
128
- lambda { provider.run_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
48
+ @provider.should_receive(:shell_out).with('dscl . -cmd /Path arguments').and_return(shell_return)
49
+ lambda { @provider.safe_dscl("cmd /Path arguments") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
129
50
  end
130
51
 
131
52
  it "raises an exception when dscl reports 'no such key'" do
132
53
  shell_return = ShellCmdResult.new("No such key: ", 'err', 23)
133
- provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
134
- lambda { provider.run_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
54
+ @provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
55
+ lambda { @provider.safe_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
135
56
  end
136
57
 
137
58
  it "raises an exception when dscl reports 'eDSRecordNotFound'" do
138
59
  shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
139
- provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
140
- lambda { provider.run_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
60
+ @provider.should_receive(:shell_out).with('dscl . -cmd /Path args').and_return(shell_return)
61
+ lambda { @provider.safe_dscl("cmd /Path args") }.should raise_error(Chef::Exceptions::DsclCommandFailed)
141
62
  end
142
63
  end
143
64
 
144
65
  describe "get_free_uid" do
145
66
  before do
146
- provider.should_receive(:run_dscl).with("list /Users uid").and_return("\nwheel 200\nstaff 201\nbrahms 500\nchopin 501\n")
67
+ @provider.stub(:safe_dscl).and_return("\nwheel 200\nstaff 201\n")
147
68
  end
148
69
 
149
- describe "when resource is configured as system" do
150
- before do
151
- new_resource.system(true)
152
- end
153
-
154
- it "should return the first unused uid number on or above 500" do
155
- provider.get_free_uid.should eq(202)
156
- end
70
+ it "should run safe_dscl with list /Users uid" do
71
+ @provider.should_receive(:safe_dscl).with("list /Users uid")
72
+ @provider.get_free_uid
157
73
  end
158
74
 
159
75
  it "should return the first unused uid number on or above 200" do
160
- provider.get_free_uid.should eq(502)
76
+ @provider.get_free_uid.should == 202
161
77
  end
162
78
 
163
79
  it "should raise an exception when the search limit is exhausted" do
164
80
  search_limit = 1
165
- lambda { provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
81
+ lambda { @provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
166
82
  end
167
83
  end
168
84
 
169
85
  describe "uid_used?" do
170
- it "should return false if not given any valid uid number" do
171
- provider.uid_used?(nil).should be_false
86
+ before do
87
+ @provider.stub(:safe_dscl).and_return("\naj 500\n")
172
88
  end
173
89
 
174
- describe "when called with a user id" do
175
- before do
176
- provider.should_receive(:run_dscl).with("list /Users uid").and_return("\naj 500\n")
177
- end
90
+ it "should run safe_dscl with list /Users uid" do
91
+ @provider.should_receive(:safe_dscl).with("list /Users uid")
92
+ @provider.uid_used?(500)
93
+ end
178
94
 
179
- it "should return true for a used uid number" do
180
- provider.uid_used?(500).should be_true
181
- end
95
+ it "should return true for a used uid number" do
96
+ @provider.uid_used?(500).should be_true
97
+ end
182
98
 
183
- it "should return false for an unused uid number" do
184
- provider.uid_used?(501).should be_false
185
- end
99
+ it "should return false for an unused uid number" do
100
+ @provider.uid_used?(501).should be_false
101
+ end
102
+
103
+ it "should return false if not given any valid uid number" do
104
+ @provider.uid_used?(nil).should be_false
186
105
  end
187
106
  end
188
107
 
189
108
  describe "when determining the uid to set" do
190
109
  it "raises RequestedUIDUnavailable if the requested uid is already in use" do
191
- provider.stub(:uid_used?).and_return(true)
192
- provider.should_receive(:get_free_uid).and_return(501)
193
- lambda { provider.dscl_set_uid }.should raise_error(Chef::Exceptions::RequestedUIDUnavailable)
110
+ @provider.stub(:uid_used?).and_return(true)
111
+ @provider.should_receive(:get_free_uid).and_return(501)
112
+ lambda { @provider.set_uid }.should raise_error(Chef::Exceptions::RequestedUIDUnavailable)
194
113
  end
195
114
 
196
115
  it "finds a valid, unused uid when none is specified" do
197
- provider.should_receive(:run_dscl).with("list /Users uid").and_return('')
198
- provider.should_receive(:run_dscl).with("create /Users/toor UniqueID 501")
199
- provider.should_receive(:get_free_uid).and_return(501)
200
- provider.dscl_set_uid
201
- new_resource.uid.should eq(501)
116
+ @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
117
+ @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 501")
118
+ @provider.should_receive(:get_free_uid).and_return(501)
119
+ @provider.set_uid
120
+ @new_resource.uid.should == 501
202
121
  end
203
122
 
204
123
  it "sets the uid specified in the resource" do
205
- new_resource.uid(1000)
206
- provider.should_receive(:run_dscl).with("create /Users/toor UniqueID 1000").and_return(true)
207
- provider.should_receive(:run_dscl).with("list /Users uid").and_return('')
208
- provider.dscl_set_uid
124
+ @new_resource.uid(1000)
125
+ @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 1000").and_return(true)
126
+ @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
127
+ @provider.set_uid
209
128
  end
210
129
  end
211
130
 
212
131
  describe "when modifying the home directory" do
213
- let(:current_resource) {
214
- new_resource.dup
215
- }
216
-
217
132
  before do
218
- new_resource.supports({ :manage_home => true })
219
- new_resource.home('/Users/toor')
133
+ @new_resource.supports({ :manage_home => true })
134
+ @new_resource.home('/Users/toor')
220
135
 
221
- provider.current_resource = current_resource
136
+ @current_resource = @new_resource.dup
137
+ @provider.current_resource = @current_resource
222
138
  end
223
139
 
224
140
  it "deletes the home directory when resource#home is nil" do
225
- new_resource.instance_variable_set(:@home, nil)
226
- provider.should_receive(:run_dscl).with("delete /Users/toor NFSHomeDirectory").and_return(true)
227
- provider.dscl_set_home
141
+ @new_resource.instance_variable_set(:@home, nil)
142
+ @provider.should_receive(:safe_dscl).with("delete /Users/toor NFSHomeDirectory").and_return(true)
143
+ @provider.modify_home
228
144
  end
229
145
 
230
146
 
231
147
  it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do
232
- new_resource.home('epic-fail')
233
- lambda { provider.dscl_set_home }.should raise_error(Chef::Exceptions::InvalidHomeDirectory)
148
+ @new_resource.home('epic-fail')
149
+ lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::InvalidHomeDirectory)
234
150
  end
235
151
 
236
152
  it "moves the users home to the new location if it exists and the target location is different" do
237
- new_resource.supports(:manage_home => true)
153
+ @new_resource.supports(:manage_home => true)
238
154
 
239
155
  current_home = CHEF_SPEC_DATA + '/old_home_dir'
240
156
  current_home_files = [current_home + '/my-dot-emacs', current_home + '/my-dot-vim']
241
- current_resource.home(current_home)
242
- new_resource.gid(23)
157
+ @current_resource.home(current_home)
158
+ @new_resource.gid(23)
243
159
  ::File.stub(:exists?).with('/old/home/toor').and_return(true)
244
160
  ::File.stub(:exists?).with('/Users/toor').and_return(true)
245
161
 
@@ -249,628 +165,316 @@ ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30"
249
165
  FileUtils.should_receive(:mv).with(current_home_files, "/Users/toor", :force => true)
250
166
  FileUtils.should_receive(:chown_R).with('toor','23','/Users/toor')
251
167
 
252
- provider.should_receive(:run_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
253
- provider.dscl_set_home
168
+ @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
169
+ @provider.modify_home
254
170
  end
255
171
 
256
172
  it "should raise an exception when the systems user template dir (skel) cannot be found" do
257
173
  ::File.stub(:exists?).and_return(false,false,false)
258
- lambda { provider.dscl_set_home }.should raise_error(Chef::Exceptions::User)
174
+ lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::User)
259
175
  end
260
176
 
261
177
  it "should run ditto to copy any missing files from skel to the new home dir" do
262
178
  ::File.should_receive(:exists?).with("/System/Library/User\ Template/English.lproj").and_return(true)
263
179
  FileUtils.should_receive(:chown_R).with('toor', '', '/Users/toor')
264
- provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
265
- provider.ditto_home
180
+ @provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
181
+ @provider.ditto_home
266
182
  end
267
183
 
268
184
  it "creates the user's NFSHomeDirectory and home directory" do
269
- provider.should_receive(:run_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'").and_return(true)
270
- provider.should_receive(:ditto_home)
271
- provider.dscl_set_home
185
+ @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'").and_return(true)
186
+ @provider.should_receive(:ditto_home)
187
+ @provider.modify_home
272
188
  end
273
189
  end
274
190
 
275
- describe "resource_requirements" do
276
- let(:dscl_exists) { true }
277
- let(:plutil_exists) { true }
278
-
279
- before do
280
- ::File.stub(:exists?).with("/usr/bin/dscl").and_return(dscl_exists)
281
- ::File.stub(:exists?).with("/usr/bin/plutil").and_return(plutil_exists)
282
- end
283
-
284
- def run_requirements
285
- provider.define_resource_requirements
286
- provider.action = :create
287
- provider.process_resource_requirements
191
+ describe "osx_shadow_hash?" do
192
+ it "should return true when the string is a shadow hash" do
193
+ @provider.osx_shadow_hash?("0"*8*155).should eql(true)
288
194
  end
289
195
 
290
- describe "when dscl doesn't exist" do
291
- let(:dscl_exists) { false }
292
-
293
- it "should raise an error" do
294
- lambda { run_requirements }.should raise_error
295
- end
296
- end
297
-
298
- describe "when plutil doesn't exist" do
299
- let(:plutil_exists) { false }
300
-
301
- it "should raise an error" do
302
- lambda { run_requirements }.should raise_error
303
- end
304
- end
305
-
306
- describe "when on Mac 10.6" do
307
- let(:mac_version) {
308
- "10.6.5"
309
- }
310
-
311
- it "should raise an error" do
312
- lambda { run_requirements }.should raise_error
313
- end
196
+ it "should return false otherwise" do
197
+ @provider.osx_shadow_hash?("any other string").should eql(false)
314
198
  end
199
+ end
315
200
 
316
- describe "when on Mac 10.7" do
317
- let(:mac_version) {
318
- "10.7.5"
319
- }
320
-
321
- describe "when password is SALTED-SHA512" do
322
- let(:password) { salted_sha512_password }
323
-
324
- it "should not raise an error" do
325
- lambda { run_requirements }.should_not raise_error
326
- end
327
- end
328
-
329
- describe "when password is SALTED-SHA512-PBKDF2" do
330
- let(:password) { salted_sha512_pbkdf2_password }
331
-
332
- it "should raise an error" do
333
- lambda { run_requirements }.should raise_error
334
- end
335
- end
201
+ describe "when detecting the format of a password" do
202
+ it "detects a OS X salted sha1" do
203
+ @provider.osx_salted_sha1?("0"*48).should eql(true)
204
+ @provider.osx_salted_sha1?("any other string").should eql(false)
336
205
  end
206
+ end
337
207
 
338
- [ "10.9", "10.10"].each do |version|
339
- describe "when on Mac #{version}" do
340
- let(:mac_version) {
341
- "#{version}.2"
342
- }
343
-
344
- describe "when password is SALTED-SHA512" do
345
- let(:password) { salted_sha512_password }
346
-
347
- it "should raise an error" do
348
- lambda { run_requirements }.should raise_error
349
- end
350
- end
351
-
352
- describe "when password is SALTED-SHA512-PBKDF2" do
353
- let(:password) { salted_sha512_pbkdf2_password }
354
-
355
- describe "when salt and iteration is not set" do
356
- it "should raise an error" do
357
- lambda { run_requirements }.should raise_error
358
- end
359
- end
360
-
361
- describe "when salt and iteration is set" do
362
- let(:salt) { salted_sha512_pbkdf2_salt }
363
- let(:iterations) { salted_sha512_pbkdf2_iterations }
364
-
365
- it "should not raise an error" do
366
- lambda { run_requirements }.should_not raise_error
367
- end
368
- end
369
- end
370
- end
208
+ describe "guid" do
209
+ it "should run safe_dscl with read /Users/user GeneratedUID to get the users GUID" do
210
+ expected_uuid = "b398449e-cee0-45e0-80f8-b0b5b1bfdeaa"
211
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(expected_uuid + "\n")
212
+ @provider.guid.should == expected_uuid
371
213
  end
372
214
  end
373
215
 
374
- describe "load_current_resource" do
375
- # set this to any of the user plist files under spec/data
376
- let(:user_plist_file) { nil }
377
-
378
- before do
379
- provider.should_receive(:shell_out).with("dscacheutil '-flushcache'")
380
- provider.should_receive(:shell_out).with("plutil -convert xml1 -o - /var/db/dslocal/nodes/Default/users/toor.plist") do
381
- if user_plist_file.nil?
382
- ShellCmdResult.new('Can not find the file', 'Sorry!!', 1)
383
- else
384
- ShellCmdResult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.plist.xml")), "", 0)
385
- end
386
- end
216
+ describe "shadow_hash_set?" do
387
217
 
388
- if !user_plist_file.nil?
389
- provider.should_receive(:convert_binary_plist_to_xml).and_return(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.shadow.xml")))
390
- end
218
+ it "should run safe_dscl with read /Users/user to see if the AuthenticationAuthority key exists" do
219
+ @provider.should_receive(:safe_dscl).with("read /Users/toor")
220
+ @provider.shadow_hash_set?
391
221
  end
392
222
 
393
- describe "when user is not there" do
394
- it "shouldn't raise an error" do
395
- lambda { provider.load_current_resource }.should_not raise_error
223
+ describe "when the user account has an AuthenticationAuthority key" do
224
+ it "uses the shadow hash when there is a ShadowHash field in the AuthenticationAuthority key" do
225
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
226
+ @provider.shadow_hash_set?.should be_true
396
227
  end
397
228
 
398
- it "should set @user_exists" do
399
- provider.load_current_resource
400
- provider.instance_variable_get(:@user_exists).should be_false
229
+ it "does not use the shadow hash when there is no ShadowHash field in the AuthenticationAuthority key" do
230
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: \n")
231
+ @provider.shadow_hash_set?.should be_false
401
232
  end
402
233
 
403
- it "should set username" do
404
- provider.load_current_resource
405
- provider.current_resource.username.should eq("toor")
406
- end
407
234
  end
408
235
 
409
- describe "when user is there" do
410
- let(:password) { "something" } # Load password during load_current_resource
411
-
412
- describe "on 10.7" do
413
- let(:mac_version) {
414
- "10.7.5"
415
- }
416
-
417
- let(:user_plist_file) { "10.7" }
418
-
419
- it "collects the user data correctly" do
420
- provider.load_current_resource
421
- provider.current_resource.comment.should eq("vagrant")
422
- provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
423
- provider.current_resource.gid.should eq("80")
424
- provider.current_resource.home.should eq("/Users/vagrant")
425
- provider.current_resource.shell.should eq("/bin/bash")
426
- provider.current_resource.password.should eq(vagrant_sha_512)
427
- end
428
-
429
- describe "when a plain password is set that is same" do
430
- let(:password) { "vagrant" }
431
-
432
- it "diverged_password? should report false" do
433
- provider.load_current_resource
434
- provider.diverged_password?.should be_false
435
- end
436
- end
437
-
438
- describe "when a plain password is set that is different" do
439
- let(:password) { "not_vagrant" }
440
-
441
- it "diverged_password? should report true" do
442
- provider.load_current_resource
443
- provider.diverged_password?.should be_true
444
- end
445
- end
446
-
447
- describe "when iterations change" do
448
- let(:password) { vagrant_sha_512 }
449
- let(:iterations) { 12345 }
450
-
451
- it "diverged_password? should report false" do
452
- provider.load_current_resource
453
- provider.diverged_password?.should be_false
454
- end
455
- end
456
-
457
- describe "when shadow hash changes" do
458
- let(:password) { salted_sha512_password }
459
-
460
- it "diverged_password? should report true" do
461
- provider.load_current_resource
462
- provider.diverged_password?.should be_true
463
- end
464
- end
465
-
466
- describe "when salt change" do
467
- let(:password) { vagrant_sha_512 }
468
- let(:salt) { "SOMETHINGRANDOM" }
469
-
470
- it "diverged_password? should report false" do
471
- provider.load_current_resource
472
- provider.diverged_password?.should be_false
473
- end
474
- end
475
- end
476
-
477
- describe "on 10.8" do
478
- let(:mac_version) {
479
- "10.8.3"
480
- }
481
-
482
- let(:user_plist_file) { "10.8" }
483
-
484
- it "collects the user data correctly" do
485
- provider.load_current_resource
486
- provider.current_resource.comment.should eq("vagrant")
487
- provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
488
- provider.current_resource.gid.should eq("80")
489
- provider.current_resource.home.should eq("/Users/vagrant")
490
- provider.current_resource.shell.should eq("/bin/bash")
491
- provider.current_resource.password.should eq("ea4c2d265d801ba0ec0dfccd\
492
- 253dfc1de91cbe0806b4acc1ed7fe22aebcf6beb5344d0f442e590\
493
- ffa04d679075da3afb119e41b72b5eaf08ee4aa54693722646d5\
494
- 19ee04843deb8a3e977428d33f625e83887913e5c13b70035961\
495
- 5e00ad7bc3e7a0c98afc3e19d1360272454f8d33a9214d2fbe8b\
496
- e68d1f9821b26689312366")
497
- provider.current_resource.salt.should eq("f994ef2f73b7c5594ebd1553300976b20733ce0e24d659783d87f3d81cbbb6a9")
498
- provider.current_resource.iterations.should eq(39840)
499
- end
500
- end
501
-
502
- describe "on 10.7 upgraded to 10.8" do
503
- # In this scenario user password is still in 10.7 format
504
- let(:mac_version) {
505
- "10.8.3"
506
- }
507
-
508
- let(:user_plist_file) { "10.7-8" }
509
-
510
- it "collects the user data correctly" do
511
- provider.load_current_resource
512
- provider.current_resource.comment.should eq("vagrant")
513
- provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
514
- provider.current_resource.gid.should eq("80")
515
- provider.current_resource.home.should eq("/Users/vagrant")
516
- provider.current_resource.shell.should eq("/bin/bash")
517
- provider.current_resource.password.should eq("6f75d7190441facc34291ebbea1fc756b242d4f\
518
- e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\
519
- ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30")
520
- end
521
-
522
- describe "when a plain text password is set" do
523
- it "reports password needs to be updated" do
524
- provider.load_current_resource
525
- provider.diverged_password?.should be_true
526
- end
527
- end
528
-
529
- describe "when a salted-sha512-pbkdf2 shadow is set" do
530
- let(:password) { salted_sha512_pbkdf2_password }
531
- let(:salt) { salted_sha512_pbkdf2_salt }
532
- let(:iterations) { salted_sha512_pbkdf2_iterations }
533
-
534
- it "reports password needs to be updated" do
535
- provider.load_current_resource
536
- provider.diverged_password?.should be_true
537
- end
538
- end
539
- end
540
-
541
- describe "on 10.9" do
542
- let(:mac_version) {
543
- "10.9.1"
544
- }
545
-
546
- let(:user_plist_file) { "10.9" }
547
-
548
- it "collects the user data correctly" do
549
- provider.load_current_resource
550
- provider.current_resource.comment.should eq("vagrant")
551
- provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
552
- provider.current_resource.gid.should eq("80")
553
- provider.current_resource.home.should eq("/Users/vagrant")
554
- provider.current_resource.shell.should eq("/bin/bash")
555
- provider.current_resource.password.should eq(vagrant_sha_512_pbkdf2)
556
- provider.current_resource.salt.should eq(vagrant_sha_512_pbkdf2_salt)
557
- provider.current_resource.iterations.should eq(vagrant_sha_512_pbkdf2_iterations)
558
- end
559
-
560
- describe "when a plain password is set that is same" do
561
- let(:password) { "vagrant" }
562
-
563
- it "diverged_password? should report false" do
564
- provider.load_current_resource
565
- provider.diverged_password?.should be_false
566
- end
567
- end
568
-
569
- describe "when a plain password is set that is different" do
570
- let(:password) { "not_vagrant" }
571
-
572
- it "diverged_password? should report true" do
573
- provider.load_current_resource
574
- provider.diverged_password?.should be_true
575
- end
576
- end
577
-
578
- describe "when iterations change" do
579
- let(:password) { vagrant_sha_512_pbkdf2 }
580
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
581
- let(:iterations) { 12345 }
582
-
583
- it "diverged_password? should report true" do
584
- provider.load_current_resource
585
- provider.diverged_password?.should be_true
586
- end
587
- end
588
-
589
- describe "when shadow hash changes" do
590
- let(:password) { salted_sha512_pbkdf2_password }
591
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
592
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
593
-
594
- it "diverged_password? should report true" do
595
- provider.load_current_resource
596
- provider.diverged_password?.should be_true
597
- end
598
- end
599
-
600
- describe "when salt change" do
601
- let(:password) { vagrant_sha_512_pbkdf2 }
602
- let(:salt) { salted_sha512_pbkdf2_salt }
603
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
604
-
605
- it "diverged_password? should report true" do
606
- provider.load_current_resource
607
- provider.diverged_password?.should be_true
608
- end
609
- end
236
+ describe "with no AuthenticationAuthority key in the user account" do
237
+ it "does not use the shadow hash" do
238
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("")
239
+ @provider.shadow_hash_set?.should eql(false)
610
240
  end
611
241
  end
612
242
  end
613
243
 
614
- describe "salted_sha512_pbkdf2?" do
615
- it "should return true when the string is a salted_sha512_pbkdf2 hash" do
616
- provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password).should be_true
617
- end
618
-
619
- it "should return false otherwise" do
620
- provider.salted_sha512_pbkdf2?(salted_sha512_password).should be_false
621
- provider.salted_sha512_pbkdf2?("any other string").should be_false
622
- end
623
- end
624
-
625
- describe "salted_sha512?" do
626
- it "should return true when the string is a salted_sha512_pbkdf2 hash" do
627
- provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password).should be_true
628
- end
629
-
630
- it "should return false otherwise" do
631
- provider.salted_sha512?(salted_sha512_pbkdf2_password).should be_false
632
- provider.salted_sha512?("any other string").should be_false
633
- end
634
- end
635
-
636
- describe "prepare_password_shadow_info" do
637
- describe "when on Mac 10.7" do
638
- let(:mac_version) {
639
- "10.7.1"
640
- }
641
-
642
- describe "when the password is plain text" do
643
- let(:password) { "vagrant" }
644
-
645
- it "password_shadow_info should have salted-sha-512 format" do
646
- shadow_info = provider.prepare_password_shadow_info
647
- shadow_info.should have_key("SALTED-SHA512")
648
- info = shadow_info["SALTED-SHA512"].string.unpack('H*').first
649
- provider.salted_sha512?(info).should be_true
650
- end
651
- end
652
-
653
- describe "when the password is salted-sha-512" do
654
- let(:password) { vagrant_sha_512 }
655
-
656
- it "password_shadow_info should have salted-sha-512 format" do
657
- shadow_info = provider.prepare_password_shadow_info
658
- shadow_info.should have_key("SALTED-SHA512")
659
- info = shadow_info["SALTED-SHA512"].string.unpack('H*').first
660
- provider.salted_sha512?(info).should be_true
661
- info.should eq(vagrant_sha_512)
662
- end
663
- end
244
+ describe "when setting or modifying the user password" do
245
+ before do
246
+ @new_resource.password("password")
247
+ @output = StringIO.new
664
248
  end
665
249
 
666
- ["10.8", "10.9", "10.10"].each do |version|
667
- describe "when on Mac #{version}" do
668
- let(:mac_version) {
669
- "#{version}.1"
670
- }
671
-
672
- describe "when the password is plain text" do
673
- let(:password) { "vagrant" }
674
-
675
- it "password_shadow_info should have salted-sha-512 format" do
676
- shadow_info = provider.prepare_password_shadow_info
677
- shadow_info.should have_key("SALTED-SHA512-PBKDF2")
678
- shadow_info["SALTED-SHA512-PBKDF2"].should have_key("entropy")
679
- shadow_info["SALTED-SHA512-PBKDF2"].should have_key("salt")
680
- shadow_info["SALTED-SHA512-PBKDF2"].should have_key("iterations")
681
- info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first
682
- provider.salted_sha512_pbkdf2?(info).should be_true
683
- end
684
- end
685
-
686
- describe "when the password is salted-sha-512" do
687
- let(:password) { vagrant_sha_512_pbkdf2 }
688
- let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
689
- let(:salt) { vagrant_sha_512_pbkdf2_salt }
690
-
691
- it "password_shadow_info should have salted-sha-512 format" do
692
- shadow_info = provider.prepare_password_shadow_info
693
- shadow_info.should have_key("SALTED-SHA512-PBKDF2")
694
- shadow_info["SALTED-SHA512-PBKDF2"].should have_key("entropy")
695
- shadow_info["SALTED-SHA512-PBKDF2"].should have_key("salt")
696
- shadow_info["SALTED-SHA512-PBKDF2"].should have_key("iterations")
697
- info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first
698
- provider.salted_sha512_pbkdf2?(info).should be_true
699
- info.should eq(vagrant_sha_512_pbkdf2)
700
- end
701
- end
702
- end
250
+ describe "when using a salted sha1 for the password" do
251
+ before do
252
+ @new_resource.password("F"*48)
253
+ end
254
+
255
+ it "should write a shadow hash file with the expected salted sha1" do
256
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
257
+ File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
258
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
259
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
260
+ expected_salted_sha1 = @new_resource.password
261
+ expected_shadow_hash = "00000000"*155
262
+ expected_shadow_hash[168] = expected_salted_sha1
263
+ @provider.modify_password
264
+ @output.string.strip.should == expected_shadow_hash
265
+ end
266
+ end
267
+
268
+ describe "when given a shadow hash file for the password" do
269
+ it "should write the shadow hash file directly to /var/db/shadow/hash/GUID" do
270
+ shadow_hash = '0123456789ABCDE0123456789ABCDEF' * 40
271
+ raise 'oops' unless shadow_hash.size == 1240
272
+ @new_resource.password shadow_hash
273
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
274
+ File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
275
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
276
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
277
+ @provider.modify_password
278
+ @output.string.strip.should == shadow_hash
279
+ end
280
+ end
281
+
282
+ describe "when given a string for the password" do
283
+ it "should output a salted sha1 and shadow hash file from the specified password" do
284
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
285
+ File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
286
+ @new_resource.password("password")
287
+ OpenSSL::Random.stub(:random_bytes).and_return("\377\377\377\377\377\377\377\377")
288
+ expected_salted_sha1 = "F"*8+"SHA1-"*8
289
+ expected_shadow_hash = "00000000"*155
290
+ expected_shadow_hash[168] = expected_salted_sha1
291
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
292
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
293
+ @provider.modify_password
294
+ @output.string.strip.should match(/^0{168}(FFFFFFFF1C1AA7935D4E1190AFEC92343F31F7671FBF126D)0{1071}$/)
295
+ end
296
+ end
297
+
298
+ it "should write the output directly to the shadow hash file at /var/db/shadow/hash/GUID" do
299
+ shadow_file = StringIO.new
300
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
301
+ File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
302
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
303
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
304
+ @provider.modify_password
305
+ shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
306
+ end
307
+
308
+ it "should run safe_dscl append /Users/user AuthenticationAuthority ;ShadowHash; when no shadow hash set" do
309
+ shadow_file = StringIO.new
310
+ uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
311
+ File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
312
+ @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
313
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority:\n")
314
+ @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';ShadowHash;'")
315
+ @provider.modify_password
316
+ shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
703
317
  end
704
318
  end
705
319
 
706
- describe "set_password" do
707
- before do
708
- new_resource.password("something")
320
+ describe "load_current_resource" do
321
+ it "should raise an error if the required binary /usr/bin/dscl doesn't exist" do
322
+ ::File.should_receive(:exists?).with("/usr/bin/dscl").and_return(false)
323
+ lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::User)
709
324
  end
710
325
 
711
- it "should sleep and flush the dscl cache before saving the password" do
712
- provider.should_receive(:prepare_password_shadow_info).and_return({ })
713
- mock_shellout = double("Mock::Shellout")
714
- mock_shellout.stub(:run_command)
715
- Mixlib::ShellOut.should_receive(:new).and_return(mock_shellout)
716
- provider.should_receive(:read_user_info)
717
- provider.should_receive(:dscl_set)
718
- provider.should_receive(:sleep).with(3)
719
- provider.should_receive(:save_user_info)
720
- provider.set_password
326
+ it "shouldn't raise an error if /usr/bin/dscl exists" do
327
+ ::File.stub(:exists?).and_return(true)
328
+ lambda { @provider.load_current_resource }.should_not raise_error
721
329
  end
722
330
  end
723
331
 
724
332
  describe "when the user does not yet exist and chef is creating it" do
725
333
  context "with a numeric gid" do
726
334
  before do
727
- new_resource.comment "#mockssuck"
728
- new_resource.gid 1001
335
+ @new_resource.comment "#mockssuck"
336
+ @new_resource.gid 1001
729
337
  end
730
338
 
731
339
  it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
732
- provider.should_receive :dscl_create_user
733
- provider.should_receive :dscl_create_comment
734
- provider.should_receive :dscl_set_uid
735
- provider.should_receive :dscl_set_gid
736
- provider.should_receive :dscl_set_home
737
- provider.should_receive :dscl_set_shell
738
- provider.should_receive :set_password
739
- provider.create_user
340
+ @provider.should_receive :dscl_create_user
341
+ @provider.should_receive :dscl_create_comment
342
+ @provider.should_receive :set_uid
343
+ @provider.should_receive :dscl_set_gid
344
+ @provider.should_receive :modify_home
345
+ @provider.should_receive :dscl_set_shell
346
+ @provider.should_receive :modify_password
347
+ @provider.create_user
740
348
  end
741
349
 
742
350
  it "creates the user and sets the comment field" do
743
- provider.should_receive(:run_dscl).with("create /Users/toor").and_return(true)
744
- provider.dscl_create_user
351
+ @provider.should_receive(:safe_dscl).with("create /Users/toor").and_return(true)
352
+ @provider.dscl_create_user
745
353
  end
746
354
 
747
355
  it "sets the comment field" do
748
- provider.should_receive(:run_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
749
- provider.dscl_create_comment
356
+ @provider.should_receive(:safe_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
357
+ @provider.dscl_create_comment
750
358
  end
751
359
 
752
- it "should run run_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
753
- provider.should_receive(:run_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true)
754
- provider.dscl_set_gid
360
+ it "should run safe_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
361
+ @provider.should_receive(:safe_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true)
362
+ @provider.dscl_set_gid
755
363
  end
756
364
 
757
- it "should run run_dscl with create /Users/user UserShell to set the users login shell" do
758
- provider.should_receive(:run_dscl).with("create /Users/toor UserShell '/usr/bin/false'").and_return(true)
759
- provider.dscl_set_shell
365
+ it "should run safe_dscl with create /Users/user UserShell to set the users login shell" do
366
+ @provider.should_receive(:safe_dscl).with("create /Users/toor UserShell '/usr/bin/false'").and_return(true)
367
+ @provider.dscl_set_shell
760
368
  end
761
369
  end
762
370
 
763
371
  context "with a non-numeric gid" do
764
372
  before do
765
- new_resource.comment "#mockssuck"
766
- new_resource.gid "newgroup"
373
+ @new_resource.comment "#mockssuck"
374
+ @new_resource.gid "newgroup"
767
375
  end
768
376
 
769
377
  it "should map the group name to a numeric ID when the group exists" do
770
- provider.should_receive(:run_dscl).with("read /Groups/newgroup PrimaryGroupID").ordered.and_return("PrimaryGroupID: 1001\n")
771
- provider.should_receive(:run_dscl).with("create /Users/toor PrimaryGroupID '1001'").ordered.and_return(true)
772
- provider.dscl_set_gid
378
+ @provider.should_receive(:safe_dscl).with("read /Groups/newgroup PrimaryGroupID").ordered.and_return("PrimaryGroupID: 1001\n")
379
+ @provider.should_receive(:safe_dscl).with("create /Users/toor PrimaryGroupID '1001'").ordered.and_return(true)
380
+ @provider.dscl_set_gid
773
381
  end
774
382
 
775
383
  it "should raise an exception when the group does not exist" do
776
384
  shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
777
- provider.should_receive(:shell_out).with('dscl . -read /Groups/newgroup PrimaryGroupID').and_return(shell_return)
778
- lambda { provider.dscl_set_gid }.should raise_error(Chef::Exceptions::GroupIDNotFound)
385
+ @provider.should_receive(:shell_out).with('dscl . -read /Groups/newgroup PrimaryGroupID').and_return(shell_return)
386
+ lambda { @provider.dscl_set_gid }.should raise_error(Chef::Exceptions::GroupIDNotFound)
779
387
  end
780
388
  end
781
389
  end
782
390
 
783
391
  describe "when the user exists and chef is managing it" do
784
392
  before do
785
- current_resource = new_resource.dup
786
- provider.current_resource = current_resource
393
+ @current_resource = @new_resource.dup
394
+ @provider.current_resource = @current_resource
787
395
 
788
- # These are all different from current_resource
789
- new_resource.username "mud"
790
- new_resource.uid 2342
791
- new_resource.gid 2342
792
- new_resource.home '/Users/death'
793
- new_resource.password 'goaway'
396
+ # These are all different from @current_resource
397
+ @new_resource.username "mud"
398
+ @new_resource.uid 2342
399
+ @new_resource.gid 2342
400
+ @new_resource.home '/Users/death'
401
+ @new_resource.password 'goaway'
794
402
  end
795
403
 
796
404
  it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do
797
- provider.should_receive :dscl_create_user
798
- provider.should_receive :dscl_create_comment
799
- provider.should_receive :dscl_set_uid
800
- provider.should_receive :dscl_set_gid
801
- provider.should_receive :dscl_set_home
802
- provider.should_receive :dscl_set_shell
803
- provider.should_receive :set_password
804
- provider.create_user
405
+ @provider.should_receive :dscl_create_user
406
+ @provider.should_receive :dscl_create_comment
407
+ @provider.should_receive :set_uid
408
+ @provider.should_receive :dscl_set_gid
409
+ @provider.should_receive :modify_home
410
+ @provider.should_receive :dscl_set_shell
411
+ @provider.should_receive :modify_password
412
+ @provider.create_user
805
413
  end
806
414
  end
807
415
 
808
416
  describe "when changing the gid" do
809
417
  before do
810
- current_resource = new_resource.dup
811
- provider.current_resource = current_resource
418
+ @current_resource = @new_resource.dup
419
+ @provider.current_resource = @current_resource
812
420
 
813
- # This is different from current_resource
814
- new_resource.gid 2342
421
+ # This is different from @current_resource
422
+ @new_resource.gid 2342
815
423
  end
816
424
 
817
425
  it "sets the gid" do
818
- provider.should_receive :dscl_set_gid
819
- provider.manage_user
426
+ @provider.should_receive :dscl_set_gid
427
+ @provider.manage_user
820
428
  end
821
429
  end
822
430
 
823
- describe "when the user exists" do
824
- before do
825
- provider.should_receive(:shell_out).with("dscacheutil '-flushcache'")
826
- provider.should_receive(:shell_out).with("plutil -convert xml1 -o - /var/db/dslocal/nodes/Default/users/toor.plist") do
827
- ShellCmdResult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/10.9.plist.xml")), "", 0)
828
- end
829
- provider.load_current_resource
830
- end
831
-
832
- describe "when Chef is removing the user" do
833
- it "removes the user from the groups and deletes home directory when the resource is configured to manage home" do
834
- new_resource.supports({ :manage_home => true })
835
- provider.should_receive(:run_dscl).with("list /Groups").and_return("my_group\nyour_group\nreal_group\n")
836
- provider.should_receive(:run_dscl).with("read /Groups/my_group").and_raise(Chef::Exceptions::DsclCommandFailed) # Empty group
837
- provider.should_receive(:run_dscl).with("read /Groups/your_group").and_return("GroupMembership: not_you")
838
- provider.should_receive(:run_dscl).with("read /Groups/real_group").and_return("GroupMembership: toor")
839
- provider.should_receive(:run_dscl).with("delete /Groups/real_group GroupMembership 'toor'")
840
- provider.should_receive(:run_dscl).with("delete /Users/toor")
841
- FileUtils.should_receive(:rm_rf).with("/Users/vagrant")
842
- provider.remove_user
843
- end
431
+ describe "when the user exists and chef is removing it" do
432
+ it "removes the user's home directory when the resource is configured to manage home" do
433
+ @new_resource.supports({ :manage_home => true })
434
+ @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("NFSHomeDirectory: /Users/fuuuuuuuuuuuuu")
435
+ @provider.should_receive(:safe_dscl).with("delete /Users/toor")
436
+ FileUtils.should_receive(:rm_rf).with("/Users/fuuuuuuuuuuuuu")
437
+ @provider.remove_user
844
438
  end
845
439
 
846
- describe "when user is not locked" do
847
- it "determines the user as not locked" do
848
- provider.should_not be_locked
849
- end
440
+ it "removes the user from any group memberships" do
441
+ Etc.stub(:group).and_yield(OpenStruct.new(:name => 'ragefisters', :mem => 'toor'))
442
+ @provider.should_receive(:safe_dscl).with("delete /Users/toor")
443
+ @provider.should_receive(:safe_dscl).with("delete /Groups/ragefisters GroupMembership 'toor'")
444
+ @provider.remove_user
850
445
  end
446
+ end
851
447
 
852
- describe "when user is locked" do
853
- before do
854
- auth_authority = provider.instance_variable_get(:@authentication_authority)
855
- provider.instance_variable_set(:@authentication_authority, auth_authority + ";DisabledUser;")
856
- end
448
+ describe "when discovering if a user is locked" do
857
449
 
858
- it "determines the user as not locked" do
859
- provider.should be_locked
860
- end
450
+ it "determines the user is not locked when dscl shows an AuthenticationAuthority without a DisabledUser field" do
451
+ @provider.should_receive(:safe_dscl).with("read /Users/toor")
452
+ @provider.should_not be_locked
453
+ end
861
454
 
862
- it "can unlock the user" do
863
- provider.should_receive(:run_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2>'")
864
- provider.unlock_user
865
- end
455
+ it "determines the user is locked when dscl shows an AuthenticationAuthority with a DisabledUser field" do
456
+ @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\nAuthenticationAuthority: ;DisabledUser;\n")
457
+ @provider.should be_locked
458
+ end
459
+
460
+ it "determines the user is not locked when dscl shows no AuthenticationAuthority" do
461
+ @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\n")
462
+ @provider.should_not be_locked
866
463
  end
867
464
  end
868
465
 
869
466
  describe "when locking the user" do
870
- it "should run run_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
871
- provider.should_receive(:run_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'")
872
- provider.lock_user
467
+ it "should run safe_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
468
+ @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'")
469
+ @provider.lock_user
873
470
  end
874
471
  end
875
472
 
473
+ describe "when unlocking the user" do
474
+ it "removes DisabledUser from the authentication string" do
475
+ @provider.should_receive(:safe_dscl).with("read /Users/toor AuthenticationAuthority").and_return("\nAuthenticationAuthority: ;ShadowHash; ;DisabledUser;\n")
476
+ @provider.should_receive(:safe_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;'")
477
+ @provider.unlock_user
478
+ end
479
+ end
876
480
  end