chef 10.32.2 → 10.34.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/distro/common/html/chef-client.8.html +4 -4
  2. data/distro/common/html/chef-expander.8.html +4 -4
  3. data/distro/common/html/chef-expanderctl.8.html +4 -4
  4. data/distro/common/html/chef-server-webui.8.html +4 -4
  5. data/distro/common/html/chef-server.8.html +4 -4
  6. data/distro/common/html/chef-solo.8.html +4 -4
  7. data/distro/common/html/chef-solr.8.html +5 -5
  8. data/distro/common/html/knife-bootstrap.1.html +4 -4
  9. data/distro/common/html/knife-client.1.html +4 -4
  10. data/distro/common/html/knife-configure.1.html +4 -4
  11. data/distro/common/html/knife-cookbook-site.1.html +4 -4
  12. data/distro/common/html/knife-cookbook.1.html +4 -4
  13. data/distro/common/html/knife-data-bag.1.html +4 -4
  14. data/distro/common/html/knife-environment.1.html +4 -4
  15. data/distro/common/html/knife-exec.1.html +4 -4
  16. data/distro/common/html/knife-index.1.html +4 -4
  17. data/distro/common/html/knife-node.1.html +4 -4
  18. data/distro/common/html/knife-role.1.html +4 -4
  19. data/distro/common/html/knife-search.1.html +4 -4
  20. data/distro/common/html/knife-ssh.1.html +4 -4
  21. data/distro/common/html/knife-status.1.html +4 -4
  22. data/distro/common/html/knife-tag.1.html +4 -4
  23. data/distro/common/html/knife.1.html +4 -4
  24. data/distro/common/html/shef.1.html +4 -4
  25. data/distro/common/man/man1/knife-bootstrap.1 +1 -1
  26. data/distro/common/man/man1/knife-client.1 +1 -1
  27. data/distro/common/man/man1/knife-configure.1 +1 -1
  28. data/distro/common/man/man1/knife-cookbook-site.1 +1 -1
  29. data/distro/common/man/man1/knife-cookbook.1 +1 -1
  30. data/distro/common/man/man1/knife-data-bag.1 +1 -1
  31. data/distro/common/man/man1/knife-environment.1 +1 -1
  32. data/distro/common/man/man1/knife-exec.1 +1 -1
  33. data/distro/common/man/man1/knife-index.1 +1 -1
  34. data/distro/common/man/man1/knife-node.1 +1 -1
  35. data/distro/common/man/man1/knife-role.1 +1 -1
  36. data/distro/common/man/man1/knife-search.1 +1 -1
  37. data/distro/common/man/man1/knife-ssh.1 +1 -1
  38. data/distro/common/man/man1/knife-status.1 +1 -1
  39. data/distro/common/man/man1/knife-tag.1 +1 -1
  40. data/distro/common/man/man1/knife.1 +1 -1
  41. data/distro/common/man/man1/shef.1 +1 -1
  42. data/distro/common/man/man8/chef-client.8 +1 -1
  43. data/distro/common/man/man8/chef-expander.8 +1 -1
  44. data/distro/common/man/man8/chef-expanderctl.8 +1 -1
  45. data/distro/common/man/man8/chef-server-webui.8 +1 -1
  46. data/distro/common/man/man8/chef-server.8 +1 -1
  47. data/distro/common/man/man8/chef-solo.8 +1 -1
  48. data/distro/common/man/man8/chef-solr.8 +1 -1
  49. data/lib/chef/client.rb +1 -2
  50. data/lib/chef/exceptions.rb +1 -0
  51. data/lib/chef/node.rb +1 -1
  52. data/lib/chef/provider/group/dscl.rb +27 -9
  53. data/lib/chef/provider/package/rpm.rb +2 -2
  54. data/lib/chef/provider/service/macosx.rb +5 -1
  55. data/lib/chef/provider/user/dscl.rb +569 -172
  56. data/lib/chef/resource/mount.rb +11 -10
  57. data/lib/chef/resource/user.rb +18 -0
  58. data/lib/chef/rest/rest_request.rb +1 -0
  59. data/lib/chef/shef.rb +7 -0
  60. data/lib/chef/shef/shef_session.rb +4 -4
  61. data/lib/chef/version.rb +1 -1
  62. data/spec/data/mac_users/10.7-8.plist.xml +559 -0
  63. data/spec/data/mac_users/10.7-8.shadow.xml +11 -0
  64. data/spec/data/mac_users/10.7.plist.xml +559 -0
  65. data/spec/data/mac_users/10.7.shadow.xml +11 -0
  66. data/spec/data/mac_users/10.8.plist.xml +559 -0
  67. data/spec/data/mac_users/10.8.shadow.xml +21 -0
  68. data/spec/data/mac_users/10.9.plist.xml +560 -0
  69. data/spec/data/mac_users/10.9.shadow.xml +21 -0
  70. data/spec/functional/resource/user/dscl_spec.rb +199 -0
  71. data/spec/spec_helper.rb +23 -0
  72. data/spec/unit/client_spec.rb +23 -4
  73. data/spec/unit/provider/group/dscl_spec.rb +35 -0
  74. data/spec/unit/provider/package/rpm_spec.rb +12 -0
  75. data/spec/unit/provider/service/macosx_spec.rb +37 -0
  76. data/spec/unit/provider/user/dscl_spec.rb +705 -284
  77. data/spec/unit/resource/mount_spec.rb +11 -0
  78. data/spec/unit/rest/auth_credentials_spec.rb +5 -0
  79. data/spec/unit/shef/shef_session_spec.rb +44 -2
  80. metadata +201 -116
  81. checksums.yaml +0 -7
@@ -0,0 +1,21 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>SALTED-SHA512-PBKDF2</key>
6
+ <dict>
7
+ <key>entropy</key>
8
+ <data>
9
+ EmAakNsXy/i6SAjmOC+w07nYpsGhkEd79oCrIa+2BlRnE25VzCCKb3QVbj2v
10
+ IPsTNp70t7r6BH2ANZ+0akikrczVSOuzOFGwk0fMqENBp/k6JxRzQ/ifuEP7
11
+ RsABfSZK+kl2uqz5QbkVvR7ByiTDCz51ngJAPgL1n+f/WTinY2w=
12
+ </data>
13
+ <key>iterations</key>
14
+ <integer>34482</integer>
15
+ <key>salt</key>
16
+ <data>
17
+ 7pVL5HL9xg3fiUhHgUM5k2JfAGr27IEMCPSafkE5RqE=
18
+ </data>
19
+ </dict>
20
+ </dict>
21
+ </plist>
@@ -0,0 +1,199 @@
1
+ #
2
+ # Copyright:: Copyright (c) 2014 Chef Software, 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
+
18
+ require 'spec_helper'
19
+ require 'chef/mixin/shell_out'
20
+ require 'functional/resource/base'
21
+
22
+ metadata = {
23
+ :unix_only => true,
24
+ :requires_root => true,
25
+ :provider => {:user => Chef::Provider::User::Dscl}
26
+ }
27
+
28
+ describe "Chef::Resource::User with Chef::Provider::User::Dscl provider", metadata do
29
+ include Chef::Mixin::ShellOut
30
+
31
+ def clean_user
32
+ begin
33
+ shell_out!("/usr/bin/dscl . -delete '/Users/#{username}'")
34
+ rescue Mixlib::ShellOut::ShellCommandFailed
35
+ # Raised when the user is already cleaned
36
+ end
37
+ end
38
+
39
+ def user_should_exist
40
+ shell_out("/usr/bin/dscl . -ls /Users").stdout.should include username
41
+ end
42
+
43
+ def check_password(pass)
44
+ # In order to test the password we use dscl passwd command since
45
+ # that's the only command that gets the user password from CLI.
46
+ shell_out("dscl . -passwd /Users/greatchef #{pass} new_password").exitstatus.should == 0
47
+ # Now reset the password back
48
+ shell_out("dscl . -passwd /Users/greatchef new_password #{pass}").exitstatus.should == 0
49
+ end
50
+
51
+ let(:node) do
52
+ n = Chef::Node.new
53
+ n.consume_external_attrs(ohai.data.dup, {})
54
+ n
55
+ end
56
+
57
+ let(:events) do
58
+ Chef::EventDispatch::Dispatcher.new
59
+ end
60
+
61
+ let(:run_context) do
62
+ Chef::RunContext.new(node, {}, events)
63
+ end
64
+
65
+ let(:username) do
66
+ "greatchef"
67
+ end
68
+
69
+ let(:uid) { nil }
70
+ let(:gid) { 20 }
71
+ let(:home) { nil }
72
+ let(:manage_home) { false }
73
+ let(:password) { "XXXYYYZZZ" }
74
+ let(:comment) { "Great Chef" }
75
+ let(:shell) { "/bin/bash" }
76
+ let(:salt) { nil }
77
+ let(:iterations) { nil }
78
+
79
+ let(:user_resource) do
80
+ r = Chef::Resource::User.new("TEST USER RESOURCE", run_context)
81
+ r.username(username)
82
+ r.uid(uid)
83
+ r.gid(gid)
84
+ r.home(home)
85
+ r.shell(shell)
86
+ r.comment(comment)
87
+ r.manage_home(manage_home)
88
+ r.password(password)
89
+ r.salt(salt)
90
+ r.iterations(iterations)
91
+ r
92
+ end
93
+
94
+ before do
95
+ clean_user
96
+ end
97
+
98
+ after(:each) do
99
+ clean_user
100
+ end
101
+
102
+ describe "action :create" do
103
+ it "should create the user" do
104
+ user_resource.run_action(:create)
105
+ user_should_exist
106
+ check_password(password)
107
+ end
108
+ end
109
+
110
+ describe "when user exists" do
111
+ before do
112
+ existing_resource = user_resource.dup
113
+ existing_resource.run_action(:create)
114
+ user_should_exist
115
+ end
116
+
117
+ describe "when password is updated" do
118
+ it "should update the password of the user" do
119
+ user_resource.password("mykitchen")
120
+ user_resource.run_action(:create)
121
+ check_password("mykitchen")
122
+ end
123
+ end
124
+ end
125
+
126
+ describe "when password is being set via shadow hash" do
127
+ let(:password) {
128
+ if node[:platform_version].start_with?("10.7.")
129
+ # On Mac 10.7 we only need to set the password
130
+ "c9b3bd1a0cde797eef0eff16c580dab996ba3a21961cccc\
131
+ d0f5e65c61558243e50b1a490088bd4824e3b35562d383ca02260398\
132
+ ef1979b302212ec1c5383d1d05fc8d843"
133
+ else
134
+ "c734b6e4787c3727bb35e29fdd92b97c\
135
+ 1de12df509577a045728255ec7c6c5f5\
136
+ c18efa05ed02b682ffa7ebc05119900e\
137
+ b1d4880833aa7a190afc13e2bf0936b8\
138
+ 20123e8c98f0f9bcac2a629d9163caac\
139
+ 9464a8c234f3919082400b4f939bb77b\
140
+ c5adbbac718b7eb99463a7b679571e0f\
141
+ 1c9fef2ef08d0b9e9c2bcf644eed2ffc"
142
+ end
143
+ }
144
+
145
+ let(:iterations) { 25000 }
146
+ let(:salt) { "9e2e7d5ee473b496fd24cf0bbfcaedfcb291ee21740e570d1e917e874f8788ca" }
147
+
148
+ it "action :create should create the user" do
149
+ user_resource.run_action(:create)
150
+ user_should_exist
151
+ check_password("soawesome")
152
+ end
153
+
154
+ describe "when user exists" do
155
+ before do
156
+ existing_resource = user_resource.dup
157
+ existing_resource.run_action(:create)
158
+ user_should_exist
159
+ end
160
+
161
+ describe "when password is updated" do
162
+ it "should update the password of the user" do
163
+ user_resource.password("mykitchen")
164
+ user_resource.run_action(:create)
165
+ check_password("mykitchen")
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ describe "when a user is member of some groups" do
172
+ let(:groups) { ["staff", "operator"] }
173
+
174
+ before do
175
+ existing_resource = user_resource.dup
176
+ existing_resource.run_action(:create)
177
+
178
+ groups.each do |group|
179
+ shell_out!("/usr/bin/dscl . -append '/Groups/#{group}' GroupMembership #{username}")
180
+ end
181
+ end
182
+
183
+ after do
184
+ groups.each do |group|
185
+ # Do not raise an error when user is correctly removed
186
+ shell_out("/usr/bin/dscl . -delete '/Groups/#{group}' GroupMembership #{username}")
187
+ end
188
+ end
189
+
190
+ it ":remove action removes the user from the groups and deletes the user"do
191
+ user_resource.run_action(:remove)
192
+ groups.each do |group|
193
+ # Do not raise an error when group is empty
194
+ shell_out("dscl . read /Groups/staff GroupMembership").stdout.should_not include(group)
195
+ end
196
+ end
197
+ end
198
+
199
+ end
@@ -51,6 +51,13 @@ require 'spec/support/local_gems.rb' if File.exists?(File.join(File.dirname(__FI
51
51
  # Explicitly require spec helpers that need to load first
52
52
  require 'spec/support/platform_helpers'
53
53
 
54
+
55
+ OHAI_SYSTEM = Ohai::System.new
56
+ OHAI_SYSTEM.require_plugin("os")
57
+ OHAI_SYSTEM.require_plugin("platform")
58
+ TEST_PLATFORM = OHAI_SYSTEM["platform"].dup.freeze
59
+ TEST_PLATFORM_VERSION = OHAI_SYSTEM["platform_version"].dup.freeze
60
+
54
61
  # Autoloads support files
55
62
  # Excludes support/platforms by default
56
63
  # Do not change the gsub.
@@ -81,6 +88,22 @@ RSpec.configure do |config|
81
88
  config.filter_run_excluding :requires_unprivileged_user => true if ENV['USER'] == 'root'
82
89
  config.filter_run_excluding :uses_diff => true unless has_diff?
83
90
 
91
+ # Functional Resource tests that are provider-specific:
92
+ # context "on platforms that use useradd", :provider => {:user => Chef::Provider::User::Useradd}} do #...
93
+ config.filter_run_excluding :provider => lambda {|criteria|
94
+ type, target_provider = criteria.first
95
+
96
+ platform = TEST_PLATFORM.dup
97
+ platform_version = TEST_PLATFORM_VERSION.dup
98
+
99
+ begin
100
+ provider_for_running_platform = Chef::Platform.find_provider(platform, platform_version, type)
101
+ provider_for_running_platform != target_provider
102
+ rescue ArgumentError # no provider for platform
103
+ true
104
+ end
105
+ }
106
+
84
107
  config.run_all_when_everything_filtered = true
85
108
  config.treat_symbols_as_metadata_keys_with_true_values = true
86
109
  end
@@ -92,7 +92,7 @@ shared_examples_for Chef::Client do
92
92
  Chef::REST.should_receive(:new).with(Chef::Config[:client_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key]).exactly(1).and_return(mock_chef_rest_for_client)
93
93
  mock_chef_rest_for_client.should_receive(:register).with(@fqdn, Chef::Config[:client_key]).exactly(1).and_return(true)
94
94
  # Client.register will then turn around create another
95
-
95
+
96
96
  # Chef::REST object, this time with the client key it got from the
97
97
  # previous step.
98
98
  Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url], @fqdn, Chef::Config[:client_key]).exactly(1).and_return(mock_chef_rest_for_node)
@@ -150,7 +150,7 @@ shared_examples_for Chef::Client do
150
150
  block.call
151
151
  end
152
152
  end
153
-
153
+
154
154
  # This is what we're testing.
155
155
  @client.run
156
156
 
@@ -159,7 +159,7 @@ shared_examples_for Chef::Client do
159
159
  @node.automatic_attrs[:platform_version].should == "example-platform-1.0"
160
160
  end
161
161
  end
162
-
162
+
163
163
  describe "when notifying other objects of the status of the chef run" do
164
164
  before do
165
165
  Chef::Client.clear_notifications
@@ -232,6 +232,25 @@ shared_examples_for Chef::Client do
232
232
  end
233
233
  end
234
234
 
235
+ describe "should set single lettered environments correctly" do
236
+ before do
237
+ @original_env = Chef::Config[:environment]
238
+ end
239
+
240
+ after do
241
+ Chef::Config[:environment] = @original_env
242
+ end
243
+
244
+ it "should set the environment correctly" do
245
+ @node.chef_environment.should == "_default"
246
+ Chef::Config[:environment] = "A"
247
+
248
+ @client.build_node
249
+
250
+ @node.chef_environment.should == "A"
251
+ end
252
+ end
253
+
235
254
  describe "when a run list override is provided" do
236
255
  before do
237
256
  @node = Chef::Node.new(@hostname)
@@ -264,7 +283,7 @@ shared_examples_for Chef::Client do
264
283
  @node.should_receive(:save).and_return(nil)
265
284
 
266
285
  @client.build_node
267
-
286
+
268
287
  @node[:roles].should_not be_nil
269
288
  @node[:roles].should eql(['test_role'])
270
289
  @node[:recipes].should eql(['cookbook1'])
@@ -293,3 +293,38 @@ describe Chef::Provider::Group::Dscl do
293
293
  end
294
294
  end
295
295
  end
296
+
297
+ describe 'Test DSCL loading' do
298
+ before do
299
+ @node = Chef::Node.new
300
+ @events = Chef::EventDispatch::Dispatcher.new
301
+ @run_context = Chef::RunContext.new(@node, {}, @events)
302
+ @new_resource = Chef::Resource::Group.new("aj")
303
+ @provider = Chef::Provider::Group::Dscl.new(@new_resource, @run_context)
304
+ @output = <<-EOF
305
+ AppleMetaNodeLocation: /Local/Default
306
+ Comment:
307
+ Test Group
308
+ GeneratedUID: AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA
309
+ NestedGroups: AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAB
310
+ Password: *
311
+ PrimaryGroupID: 999
312
+ RealName:
313
+ TestGroup
314
+ RecordName: com.apple.aj
315
+ RecordType: dsRecTypeStandard:Groups
316
+ GroupMembership: waka bar
317
+ EOF
318
+ @provider.stub(:safe_dscl).with("read /Groups/aj").and_return(@output)
319
+ @current_resource = @provider.load_current_resource
320
+ end
321
+
322
+ it 'should parse gid properly' do
323
+ File.stub(:exists?).and_return(true)
324
+ @current_resource.gid.should eq("999")
325
+ end
326
+ it 'should parse members properly' do
327
+ File.stub(:exists?).and_return(true)
328
+ @current_resource.members.should eq(['waka', 'bar'])
329
+ end
330
+ end
@@ -80,6 +80,18 @@ describe Chef::Provider::Package::Rpm do
80
80
  @provider.stub!(:popen4).and_return(status)
81
81
  lambda { @provider.run_action(:any) }.should raise_error(Chef::Exceptions::Package)
82
82
  end
83
+
84
+ it "should not detect the package name as version when not installed" do
85
+ @status = double("Status", :exitstatus => -1)
86
+ @stdout = StringIO.new("package openssh-askpass is not installed")
87
+ @new_resource = Chef::Resource::Package.new("openssh-askpass")
88
+ @new_resource.source 'openssh-askpass'
89
+ @provider = Chef::Provider::Package::Rpm.new(@new_resource, @run_context)
90
+ @provider.should_receive(:popen4).with("rpm -qp --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status)
91
+ @provider.should_receive(:popen4).with("rpm -q --queryformat '%{NAME} %{VERSION}-%{RELEASE}\n' openssh-askpass").and_return(@status)
92
+ @provider.load_current_resource
93
+ @provider.current_resource.version.should be_nil
94
+ end
83
95
  end
84
96
 
85
97
  describe "after the current resource is loaded" do
@@ -58,6 +58,43 @@ XML
58
58
  let!(:current_resource) { Chef::Resource::Service.new(service_name) }
59
59
 
60
60
  describe "#load_current_resource" do
61
+
62
+ # CHEF-5223 "you can't glob for a file that hasn't been converged
63
+ # onto the node yet."
64
+ context "when the plist doesn't exist" do
65
+
66
+ def run_resource_setup_for_action(action)
67
+ new_resource.action(action)
68
+ provider.action = action
69
+ provider.load_current_resource
70
+ provider.define_resource_requirements
71
+ provider.process_resource_requirements
72
+ end
73
+
74
+ before do
75
+ Dir.stub(:glob).and_return([])
76
+ provider.stub(:shell_out!).
77
+ with(/plutil -convert xml1 -o/).
78
+ and_raise(Mixlib::ShellOut::ShellCommandFailed)
79
+ end
80
+
81
+ it "works for action :nothing" do
82
+ lambda { run_resource_setup_for_action(:nothing) }.should_not raise_error
83
+ end
84
+
85
+ it "works for action :start" do
86
+ lambda { run_resource_setup_for_action(:start) }.should_not raise_error
87
+ end
88
+
89
+ it "errors if action is :enable" do
90
+ lambda { run_resource_setup_for_action(:enable) }.should raise_error(Chef::Exceptions::Service)
91
+ end
92
+
93
+ it "errors if action is :disable" do
94
+ lambda { run_resource_setup_for_action(:disable) }.should raise_error(Chef::Exceptions::Service)
95
+ end
96
+ end
97
+
61
98
  context "when launchctl returns pid in service list" do
62
99
  let(:launchctl_stdout) { StringIO.new <<-SVC_LIST }
63
100
  12761 - 0x100114220.old.machinit.thing
@@ -6,9 +6,9 @@
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
8
8
  # You may obtain a copy of the License at
9
- #
9
+ #
10
10
  # http://www.apache.org/licenses/LICENSE-2.0
11
- #
11
+ #
12
12
  # Unless required by applicable law or agreed to in writing, software
13
13
  # distributed under the License is distributed on an "AS IS" BASIS,
14
14
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,435 +20,856 @@ ShellCmdResult = Struct.new(:stdout, :stderr, :exitstatus)
20
20
 
21
21
  require 'spec_helper'
22
22
  require 'ostruct'
23
+ require 'mixlib/shellout'
23
24
 
24
25
  describe Chef::Provider::User::Dscl do
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
32
-
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
+ }
111
+
33
112
  describe "when shelling out to dscl" do
34
113
  it "should run dscl with the supplied cmd /Path args" do
35
114
  shell_return = ShellCmdResult.new('stdout', 'err', 0)
36
- @provider.should_receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return)
37
- @provider.safe_dscl("cmd /Path args").should == 'stdout'
115
+ provider.should_receive(:shell_out).with("dscl . -cmd /Path args").and_return(shell_return)
116
+ provider.run_dscl("cmd /Path args").should == 'stdout'
38
117
  end
39
118
 
40
119
  it "returns an empty string from delete commands" do
41
120
  shell_return = ShellCmdResult.new('out', 'err', 23)
42
- @provider.should_receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return)
43
- @provider.safe_dscl("delete /Path args").should == ""
121
+ provider.should_receive(:shell_out).with("dscl . -delete /Path args").and_return(shell_return)
122
+ provider.run_dscl("delete /Path args").should == ""
44
123
  end
45
124
 
46
125
  it "should raise an exception for any other command" do
47
126
  shell_return = ShellCmdResult.new('out', 'err', 23)
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)
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)
50
129
  end
51
130
 
52
131
  it "raises an exception when dscl reports 'no such key'" do
53
132
  shell_return = ShellCmdResult.new("No such key: ", 'err', 23)
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)
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)
135
+ end
136
+
137
+ it "raises an exception when dscl reports 'eDSRecordNotFound'" do
138
+ 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)
56
141
  end
57
142
  end
58
143
 
59
144
  describe "get_free_uid" do
60
145
  before do
61
- @provider.stub!(:safe_dscl).and_return("\nwheel 200\nstaff 201\n")
146
+ provider.should_receive(:run_dscl).with("list /Users uid").and_return("\nwheel 200\nstaff 201\nbrahms 500\nchopin 501\n")
62
147
  end
63
-
64
- it "should run safe_dscl with list /Users uid" do
65
- @provider.should_receive(:safe_dscl).with("list /Users uid")
66
- @provider.get_free_uid
148
+
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
67
157
  end
68
158
 
69
159
  it "should return the first unused uid number on or above 200" do
70
- @provider.get_free_uid.should == 202
160
+ provider.get_free_uid.should eq(502)
71
161
  end
72
-
162
+
73
163
  it "should raise an exception when the search limit is exhausted" do
74
164
  search_limit = 1
75
- lambda { @provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
165
+ lambda { provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
76
166
  end
77
167
  end
78
168
 
79
169
  describe "uid_used?" do
80
- before do
81
- @provider.stub!(:safe_dscl).and_return("\naj 500\n")
170
+ it "should return false if not given any valid uid number" do
171
+ provider.uid_used?(nil).should be_false
82
172
  end
83
173
 
84
- it "should run safe_dscl with list /Users uid" do
85
- @provider.should_receive(:safe_dscl).with("list /Users uid")
86
- @provider.uid_used?(500)
87
- end
88
-
89
- it "should return true for a used uid number" do
90
- @provider.uid_used?(500).should be_true
91
- end
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
92
178
 
93
- it "should return false for an unused uid number" do
94
- @provider.uid_used?(501).should be_false
95
- end
179
+ it "should return true for a used uid number" do
180
+ provider.uid_used?(500).should be_true
181
+ end
96
182
 
97
- it "should return false if not given any valid uid number" do
98
- @provider.uid_used?(nil).should be_false
183
+ it "should return false for an unused uid number" do
184
+ provider.uid_used?(501).should be_false
185
+ end
99
186
  end
100
187
  end
101
188
 
102
189
  describe "when determining the uid to set" do
103
190
  it "raises RequestedUIDUnavailable if the requested uid is already in use" do
104
- @provider.stub!(:uid_used?).and_return(true)
105
- @provider.should_receive(:get_free_uid).and_return(501)
106
- lambda { @provider.set_uid }.should raise_error(Chef::Exceptions::RequestedUIDUnavailable)
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)
107
194
  end
108
-
195
+
109
196
  it "finds a valid, unused uid when none is specified" do
110
- @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
111
- @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 501")
112
- @provider.should_receive(:get_free_uid).and_return(501)
113
- @provider.set_uid
114
- @new_resource.uid.should == 501
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)
115
202
  end
116
-
203
+
117
204
  it "sets the uid specified in the resource" do
118
- @new_resource.uid(1000)
119
- @provider.should_receive(:safe_dscl).with("create /Users/toor UniqueID 1000").and_return(true)
120
- @provider.should_receive(:safe_dscl).with("list /Users uid").and_return('')
121
- @provider.set_uid
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
122
209
  end
123
210
  end
124
211
 
125
212
  describe "when modifying the home directory" do
213
+ let(:current_resource) {
214
+ new_resource.dup
215
+ }
216
+
126
217
  before do
127
- @new_resource.supports({ :manage_home => true })
128
- @new_resource.home('/Users/toor')
129
-
130
- @current_resource = @new_resource.dup
131
- @provider.current_resource = @current_resource
218
+ new_resource.supports({ :manage_home => true })
219
+ new_resource.home('/Users/toor')
220
+
221
+ provider.current_resource = current_resource
132
222
  end
133
223
 
134
224
  it "deletes the home directory when resource#home is nil" do
135
- @new_resource.instance_variable_set(:@home, nil)
136
- @provider.should_receive(:safe_dscl).with("delete /Users/toor NFSHomeDirectory").and_return(true)
137
- @provider.modify_home
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
138
228
  end
139
-
229
+
140
230
 
141
231
  it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do
142
- @new_resource.home('epic-fail')
143
- lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::InvalidHomeDirectory)
232
+ new_resource.home('epic-fail')
233
+ lambda { provider.dscl_set_home }.should raise_error(Chef::Exceptions::InvalidHomeDirectory)
144
234
  end
145
235
 
146
236
  it "moves the users home to the new location if it exists and the target location is different" do
147
- @new_resource.supports(:manage_home => true)
148
-
237
+ new_resource.supports(:manage_home => true)
238
+
149
239
  current_home = CHEF_SPEC_DATA + '/old_home_dir'
150
240
  current_home_files = [current_home + '/my-dot-emacs', current_home + '/my-dot-vim']
151
- @current_resource.home(current_home)
152
- @new_resource.gid(23)
153
- ::File.stub!(:exists?).with('/old/home/toor').and_return(true)
154
- ::File.stub!(:exists?).with('/Users/toor').and_return(true)
155
-
241
+ current_resource.home(current_home)
242
+ new_resource.gid(23)
243
+ ::File.stub(:exists?).with('/old/home/toor').and_return(true)
244
+ ::File.stub(:exists?).with('/Users/toor').and_return(true)
245
+
156
246
  FileUtils.should_receive(:mkdir_p).with('/Users/toor').and_return(true)
157
247
  FileUtils.should_receive(:rmdir).with(current_home)
158
248
  ::Dir.should_receive(:glob).with("#{CHEF_SPEC_DATA}/old_home_dir/*",::File::FNM_DOTMATCH).and_return(current_home_files)
159
249
  FileUtils.should_receive(:mv).with(current_home_files, "/Users/toor", :force => true)
160
250
  FileUtils.should_receive(:chown_R).with('toor','23','/Users/toor')
161
-
162
- @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
163
- @provider.modify_home
251
+
252
+ provider.should_receive(:run_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
253
+ provider.dscl_set_home
164
254
  end
165
255
 
166
256
  it "should raise an exception when the systems user template dir (skel) cannot be found" do
167
- ::File.stub!(:exists?).and_return(false,false,false)
168
- lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::User)
257
+ ::File.stub(:exists?).and_return(false,false,false)
258
+ lambda { provider.dscl_set_home }.should raise_error(Chef::Exceptions::User)
169
259
  end
170
260
 
171
261
  it "should run ditto to copy any missing files from skel to the new home dir" do
172
262
  ::File.should_receive(:exists?).with("/System/Library/User\ Template/English.lproj").and_return(true)
173
263
  FileUtils.should_receive(:chown_R).with('toor', '', '/Users/toor')
174
- @provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
175
- @provider.ditto_home
264
+ provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
265
+ provider.ditto_home
176
266
  end
177
267
 
178
268
  it "creates the user's NFSHomeDirectory and home directory" do
179
- @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'").and_return(true)
180
- @provider.should_receive(:ditto_home)
181
- @provider.modify_home
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
182
272
  end
183
273
  end
184
274
 
185
- describe "osx_shadow_hash?" do
186
- it "should return true when the string is a shadow hash" do
187
- @provider.osx_shadow_hash?("0"*8*155).should eql(true)
188
- end
275
+ describe "resource_requirements" do
276
+ let(:dscl_exists) { true }
277
+ let(:plutil_exists) { true }
189
278
 
190
- it "should return false otherwise" do
191
- @provider.osx_shadow_hash?("any other string").should eql(false)
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)
192
282
  end
193
- end
194
283
 
195
- describe "when detecting the format of a password" do
196
- it "detects a OS X salted sha1" do
197
- @provider.osx_salted_sha1?("0"*48).should eql(true)
198
- @provider.osx_salted_sha1?("any other string").should eql(false)
284
+ def run_requirements
285
+ provider.define_resource_requirements
286
+ provider.action = :create
287
+ provider.process_resource_requirements
199
288
  end
200
- end
201
289
 
202
- describe "guid" do
203
- it "should run safe_dscl with read /Users/user GeneratedUID to get the users GUID" do
204
- expected_uuid = "b398449e-cee0-45e0-80f8-b0b5b1bfdeaa"
205
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(expected_uuid + "\n")
206
- @provider.guid.should == expected_uuid
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
207
296
  end
208
- end
209
297
 
210
- describe "shadow_hash_set?" do
298
+ describe "when plutil doesn't exist" do
299
+ let(:plutil_exists) { false }
211
300
 
212
- it "should run safe_dscl with read /Users/user to see if the AuthenticationAuthority key exists" do
213
- @provider.should_receive(:safe_dscl).with("read /Users/toor")
214
- @provider.shadow_hash_set?
301
+ it "should raise an error" do
302
+ lambda { run_requirements }.should raise_error
303
+ end
215
304
  end
216
305
 
217
- describe "when the user account has an AuthenticationAuthority key" do
218
- it "uses the shadow hash when there is a ShadowHash field in the AuthenticationAuthority key" do
219
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
220
- @provider.shadow_hash_set?.should be_true
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
221
313
  end
314
+ end
315
+
316
+ describe "when on Mac 10.7" do
317
+ let(:mac_version) {
318
+ "10.7.5"
319
+ }
222
320
 
223
- it "does not use the shadow hash when there is no ShadowHash field in the AuthenticationAuthority key" do
224
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: \n")
225
- @provider.shadow_hash_set?.should be_false
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
226
327
  end
227
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
228
336
  end
229
337
 
230
- describe "with no AuthenticationAuthority key in the user account" do
231
- it "does not use the shadow hash" do
232
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("")
233
- @provider.shadow_hash_set?.should eql(false)
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
234
370
  end
235
371
  end
236
372
  end
237
373
 
238
- describe "when setting or modifying the user password" do
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
+
239
378
  before do
240
- @new_resource.password("password")
241
- @output = StringIO.new
379
+ provider.should_receive(:shell_out).with("plutil -convert xml1 -o - /var/db/dslocal/nodes/Default/users/toor.plist") do
380
+ if user_plist_file.nil?
381
+ ShellCmdResult.new('Can not find the file', 'Sorry!!', 1)
382
+ else
383
+ ShellCmdResult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.plist.xml")), "", 0)
384
+ end
385
+ end
386
+
387
+ if !user_plist_file.nil?
388
+ provider.should_receive(:convert_binary_plist_to_xml).and_return(File.read(File.join(CHEF_SPEC_DATA, "mac_users/#{user_plist_file}.shadow.xml")))
389
+ end
242
390
  end
243
391
 
244
- describe "when using a salted sha1 for the password" do
245
- before do
246
- @new_resource.password("F"*48)
247
- end
248
-
249
- it "should write a shadow hash file with the expected salted sha1" do
250
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
251
- File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
252
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
253
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
254
- expected_salted_sha1 = @new_resource.password
255
- expected_shadow_hash = "00000000"*155
256
- expected_shadow_hash[168] = expected_salted_sha1
257
- @provider.modify_password
258
- @output.string.strip.should == expected_shadow_hash
259
- end
260
- end
261
-
262
- describe "when given a shadow hash file for the password" do
263
- it "should write the shadow hash file directly to /var/db/shadow/hash/GUID" do
264
- shadow_hash = '0123456789ABCDE0123456789ABCDEF' * 40
265
- raise 'oops' unless shadow_hash.size == 1240
266
- @new_resource.password shadow_hash
267
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
268
- File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
269
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
270
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
271
- @provider.modify_password
272
- @output.string.strip.should == shadow_hash
273
- end
274
- end
275
-
276
- describe "when given a string for the password" do
277
- it "should output a salted sha1 and shadow hash file from the specified password" do
278
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
279
- File.should_receive(:open).with('/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA', "w", 384).and_yield(@output)
280
- @new_resource.password("password")
281
- OpenSSL::Random.stub!(:random_bytes).and_return("\377\377\377\377\377\377\377\377")
282
- expected_salted_sha1 = "F"*8+"SHA1-"*8
283
- expected_shadow_hash = "00000000"*155
284
- expected_shadow_hash[168] = expected_salted_sha1
285
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
286
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
287
- @provider.modify_password
288
- @output.string.strip.should match(/^0{168}(FFFFFFFF1C1AA7935D4E1190AFEC92343F31F7671FBF126D)0{1071}$/)
289
- end
290
- end
291
-
292
- it "should write the output directly to the shadow hash file at /var/db/shadow/hash/GUID" do
293
- shadow_file = StringIO.new
294
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
295
- File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
296
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
297
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority: ;ShadowHash;\n")
298
- @provider.modify_password
299
- shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
300
- end
301
-
302
- it "should run safe_dscl append /Users/user AuthenticationAuthority ;ShadowHash; when no shadow hash set" do
303
- shadow_file = StringIO.new
304
- uuid = "B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA"
305
- File.should_receive(:open).with("/var/db/shadow/hash/B398449E-CEE0-45E0-80F8-B0B5B1BFDEAA",'w',0600).and_yield(shadow_file)
306
- @provider.should_receive(:safe_dscl).with("read /Users/toor GeneratedUID").and_return(uuid)
307
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("\nAuthenticationAuthority:\n")
308
- @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';ShadowHash;'")
309
- @provider.modify_password
310
- shadow_file.string.should match(/^0{168}[0-9A-F]{48}0{1071}$/)
392
+ describe "when user is not there" do
393
+ it "shouldn't raise an error" do
394
+ lambda { provider.load_current_resource }.should_not raise_error
395
+ end
396
+
397
+ it "should set @user_exists" do
398
+ provider.load_current_resource
399
+ provider.instance_variable_get(:@user_exists).should be_false
400
+ end
401
+
402
+ it "should set username" do
403
+ provider.load_current_resource
404
+ provider.current_resource.username.should eq("toor")
405
+ end
406
+ end
407
+
408
+ describe "when user is there" do
409
+ let(:password) { "something" } # Load password during load_current_resource
410
+
411
+ describe "on 10.7" do
412
+ let(:mac_version) {
413
+ "10.7.5"
414
+ }
415
+
416
+ let(:user_plist_file) { "10.7" }
417
+
418
+ it "collects the user data correctly" do
419
+ provider.load_current_resource
420
+ provider.current_resource.comment.should eq("vagrant")
421
+ provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
422
+ provider.current_resource.gid.should eq("80")
423
+ provider.current_resource.home.should eq("/Users/vagrant")
424
+ provider.current_resource.shell.should eq("/bin/bash")
425
+ provider.current_resource.password.should eq(vagrant_sha_512)
426
+ end
427
+
428
+ describe "when a plain password is set that is same" do
429
+ let(:password) { "vagrant" }
430
+
431
+ it "diverged_password? should report false" do
432
+ provider.load_current_resource
433
+ provider.diverged_password?.should be_false
434
+ end
435
+ end
436
+
437
+ describe "when a plain password is set that is different" do
438
+ let(:password) { "not_vagrant" }
439
+
440
+ it "diverged_password? should report true" do
441
+ provider.load_current_resource
442
+ provider.diverged_password?.should be_true
443
+ end
444
+ end
445
+
446
+ describe "when iterations change" do
447
+ let(:password) { vagrant_sha_512 }
448
+ let(:iterations) { 12345 }
449
+
450
+ it "diverged_password? should report false" do
451
+ provider.load_current_resource
452
+ provider.diverged_password?.should be_false
453
+ end
454
+ end
455
+
456
+ describe "when shadow hash changes" do
457
+ let(:password) { salted_sha512_password }
458
+
459
+ it "diverged_password? should report true" do
460
+ provider.load_current_resource
461
+ provider.diverged_password?.should be_true
462
+ end
463
+ end
464
+
465
+ describe "when salt change" do
466
+ let(:password) { vagrant_sha_512 }
467
+ let(:salt) { "SOMETHINGRANDOM" }
468
+
469
+ it "diverged_password? should report false" do
470
+ provider.load_current_resource
471
+ provider.diverged_password?.should be_false
472
+ end
473
+ end
474
+ end
475
+
476
+ describe "on 10.8" do
477
+ let(:mac_version) {
478
+ "10.8.3"
479
+ }
480
+
481
+ let(:user_plist_file) { "10.8" }
482
+
483
+ it "collects the user data correctly" do
484
+ provider.load_current_resource
485
+ provider.current_resource.comment.should eq("vagrant")
486
+ provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
487
+ provider.current_resource.gid.should eq("80")
488
+ provider.current_resource.home.should eq("/Users/vagrant")
489
+ provider.current_resource.shell.should eq("/bin/bash")
490
+ provider.current_resource.password.should eq("ea4c2d265d801ba0ec0dfccd\
491
+ 253dfc1de91cbe0806b4acc1ed7fe22aebcf6beb5344d0f442e590\
492
+ ffa04d679075da3afb119e41b72b5eaf08ee4aa54693722646d5\
493
+ 19ee04843deb8a3e977428d33f625e83887913e5c13b70035961\
494
+ 5e00ad7bc3e7a0c98afc3e19d1360272454f8d33a9214d2fbe8b\
495
+ e68d1f9821b26689312366")
496
+ provider.current_resource.salt.should eq("f994ef2f73b7c5594ebd1553300976b20733ce0e24d659783d87f3d81cbbb6a9")
497
+ provider.current_resource.iterations.should eq(39840)
498
+ end
499
+ end
500
+
501
+ describe "on 10.7 upgraded to 10.8" do
502
+ # In this scenario user password is still in 10.7 format
503
+ let(:mac_version) {
504
+ "10.8.3"
505
+ }
506
+
507
+ let(:user_plist_file) { "10.7-8" }
508
+
509
+ it "collects the user data correctly" do
510
+ provider.load_current_resource
511
+ provider.current_resource.comment.should eq("vagrant")
512
+ provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
513
+ provider.current_resource.gid.should eq("80")
514
+ provider.current_resource.home.should eq("/Users/vagrant")
515
+ provider.current_resource.shell.should eq("/bin/bash")
516
+ provider.current_resource.password.should eq("6f75d7190441facc34291ebbea1fc756b242d4f\
517
+ e9bcff141bccb84f1979e27e539539aa31f9f7dcc92c0cea959\
518
+ ea18e18b720e358e7fbe3cfbeaa561456f6ba008937a30")
519
+ end
520
+
521
+ describe "when a plain text password is set" do
522
+ it "reports password needs to be updated" do
523
+ provider.load_current_resource
524
+ provider.diverged_password?.should be_true
525
+ end
526
+ end
527
+
528
+ describe "when a salted-sha512-pbkdf2 shadow is set" do
529
+ let(:password) { salted_sha512_pbkdf2_password }
530
+ let(:salt) { salted_sha512_pbkdf2_salt }
531
+ let(:iterations) { salted_sha512_pbkdf2_iterations }
532
+
533
+ it "reports password needs to be updated" do
534
+ provider.load_current_resource
535
+ provider.diverged_password?.should be_true
536
+ end
537
+ end
538
+ end
539
+
540
+ describe "on 10.9" do
541
+ let(:mac_version) {
542
+ "10.9.1"
543
+ }
544
+
545
+ let(:user_plist_file) { "10.9" }
546
+
547
+ it "collects the user data correctly" do
548
+ provider.load_current_resource
549
+ provider.current_resource.comment.should eq("vagrant")
550
+ provider.current_resource.uid.should eq("11112222-3333-4444-AAAA-BBBBCCCCDDDD")
551
+ provider.current_resource.gid.should eq("80")
552
+ provider.current_resource.home.should eq("/Users/vagrant")
553
+ provider.current_resource.shell.should eq("/bin/bash")
554
+ provider.current_resource.password.should eq(vagrant_sha_512_pbkdf2)
555
+ provider.current_resource.salt.should eq(vagrant_sha_512_pbkdf2_salt)
556
+ provider.current_resource.iterations.should eq(vagrant_sha_512_pbkdf2_iterations)
557
+ end
558
+
559
+ describe "when a plain password is set that is same" do
560
+ let(:password) { "vagrant" }
561
+
562
+ it "diverged_password? should report false" do
563
+ provider.load_current_resource
564
+ provider.diverged_password?.should be_false
565
+ end
566
+ end
567
+
568
+ describe "when a plain password is set that is different" do
569
+ let(:password) { "not_vagrant" }
570
+
571
+ it "diverged_password? should report true" do
572
+ provider.load_current_resource
573
+ provider.diverged_password?.should be_true
574
+ end
575
+ end
576
+
577
+ describe "when iterations change" do
578
+ let(:password) { vagrant_sha_512_pbkdf2 }
579
+ let(:salt) { vagrant_sha_512_pbkdf2_salt }
580
+ let(:iterations) { 12345 }
581
+
582
+ it "diverged_password? should report true" do
583
+ provider.load_current_resource
584
+ provider.diverged_password?.should be_true
585
+ end
586
+ end
587
+
588
+ describe "when shadow hash changes" do
589
+ let(:password) { salted_sha512_pbkdf2_password }
590
+ let(:salt) { vagrant_sha_512_pbkdf2_salt }
591
+ let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
592
+
593
+ it "diverged_password? should report true" do
594
+ provider.load_current_resource
595
+ provider.diverged_password?.should be_true
596
+ end
597
+ end
598
+
599
+ describe "when salt change" do
600
+ let(:password) { vagrant_sha_512_pbkdf2 }
601
+ let(:salt) { salted_sha512_pbkdf2_salt }
602
+ let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
603
+
604
+ it "diverged_password? should report true" do
605
+ provider.load_current_resource
606
+ provider.diverged_password?.should be_true
607
+ end
608
+ end
609
+ end
311
610
  end
312
611
  end
313
612
 
314
- describe "load_current_resource" do
315
- it "should raise an error if the required binary /usr/bin/dscl doesn't exist" do
316
- ::File.should_receive(:exists?).with("/usr/bin/dscl").and_return(false)
317
- lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::User)
613
+ describe "salted_sha512_pbkdf2?" do
614
+ it "should return true when the string is a salted_sha512_pbkdf2 hash" do
615
+ provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password).should be_true
318
616
  end
319
617
 
320
- it "shouldn't raise an error if /usr/bin/dscl exists" do
321
- ::File.stub!(:exists?).and_return(true)
322
- lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::User)
618
+ it "should return false otherwise" do
619
+ provider.salted_sha512_pbkdf2?(salted_sha512_password).should be_false
620
+ provider.salted_sha512_pbkdf2?("any other string").should be_false
323
621
  end
324
622
  end
325
623
 
326
- describe "when the user does not yet exist and chef is creating it" do
327
- before do
328
- @new_resource.comment "#mockssuck"
329
- @new_resource.gid 1001
624
+ describe "salted_sha512?" do
625
+ it "should return true when the string is a salted_sha512_pbkdf2 hash" do
626
+ provider.salted_sha512_pbkdf2?(salted_sha512_pbkdf2_password).should be_true
627
+ end
628
+
629
+ it "should return false otherwise" do
630
+ provider.salted_sha512?(salted_sha512_pbkdf2_password).should be_false
631
+ provider.salted_sha512?("any other string").should be_false
330
632
  end
331
-
332
- it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
333
- @provider.should_receive :dscl_create_user
334
- @provider.should_receive :dscl_create_comment
335
- @provider.should_receive :set_uid
336
- @provider.should_receive :dscl_set_gid
337
- @provider.should_receive :modify_home
338
- @provider.should_receive :dscl_set_shell
339
- @provider.should_receive :modify_password
340
- @provider.create_user
633
+ end
634
+
635
+ describe "prepare_password_shadow_info" do
636
+ describe "when on Mac 10.7" do
637
+ let(:mac_version) {
638
+ "10.7.1"
639
+ }
640
+
641
+ describe "when the password is plain text" do
642
+ let(:password) { "vagrant" }
643
+
644
+ it "password_shadow_info should have salted-sha-512 format" do
645
+ shadow_info = provider.prepare_password_shadow_info
646
+ shadow_info.should have_key("SALTED-SHA512")
647
+ info = shadow_info["SALTED-SHA512"].string.unpack('H*').first
648
+ provider.salted_sha512?(info).should be_true
649
+ end
650
+ end
651
+
652
+ describe "when the password is salted-sha-512" do
653
+ let(:password) { vagrant_sha_512 }
654
+
655
+ it "password_shadow_info should have salted-sha-512 format" do
656
+ shadow_info = provider.prepare_password_shadow_info
657
+ shadow_info.should have_key("SALTED-SHA512")
658
+ info = shadow_info["SALTED-SHA512"].string.unpack('H*').first
659
+ provider.salted_sha512?(info).should be_true
660
+ info.should eq(vagrant_sha_512)
661
+ end
662
+ end
341
663
  end
342
664
 
343
- it "creates the user and sets the comment field" do
344
- @provider.should_receive(:safe_dscl).with("create /Users/toor").and_return(true)
345
- @provider.dscl_create_user
665
+ ["10.8", "10.9", "10.10"].each do |version|
666
+ describe "when on Mac #{version}" do
667
+ let(:mac_version) {
668
+ "#{version}.1"
669
+ }
670
+
671
+ describe "when the password is plain text" do
672
+ let(:password) { "vagrant" }
673
+
674
+ it "password_shadow_info should have salted-sha-512 format" do
675
+ shadow_info = provider.prepare_password_shadow_info
676
+ shadow_info.should have_key("SALTED-SHA512-PBKDF2")
677
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("entropy")
678
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("salt")
679
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("iterations")
680
+ info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first
681
+ provider.salted_sha512_pbkdf2?(info).should be_true
682
+ end
683
+ end
684
+
685
+ describe "when the password is salted-sha-512" do
686
+ let(:password) { vagrant_sha_512_pbkdf2 }
687
+ let(:iterations) { vagrant_sha_512_pbkdf2_iterations }
688
+ let(:salt) { vagrant_sha_512_pbkdf2_salt }
689
+
690
+ it "password_shadow_info should have salted-sha-512 format" do
691
+ shadow_info = provider.prepare_password_shadow_info
692
+ shadow_info.should have_key("SALTED-SHA512-PBKDF2")
693
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("entropy")
694
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("salt")
695
+ shadow_info["SALTED-SHA512-PBKDF2"].should have_key("iterations")
696
+ info = shadow_info["SALTED-SHA512-PBKDF2"]["entropy"].string.unpack('H*').first
697
+ provider.salted_sha512_pbkdf2?(info).should be_true
698
+ info.should eq(vagrant_sha_512_pbkdf2)
699
+ end
700
+ end
701
+ end
346
702
  end
347
-
348
- it "sets the comment field" do
349
- @provider.should_receive(:safe_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
350
- @provider.dscl_create_comment
703
+ end
704
+
705
+ describe "set_password" do
706
+ before do
707
+ new_resource.password("something")
351
708
  end
352
709
 
353
- it "should run safe_dscl with create /Users/user PrimaryGroupID to set the users primary group" do
354
- @provider.should_receive(:safe_dscl).with("create /Users/toor PrimaryGroupID '1001'").and_return(true)
355
- @provider.dscl_set_gid
710
+ it "should sleep and flush the dscl cache before saving the password" do
711
+ provider.should_receive(:prepare_password_shadow_info).and_return({ })
712
+ mock_shellout = double("Mock::Shellout")
713
+ mock_shellout.stub(:run_command)
714
+ Mixlib::ShellOut.should_receive(:new).and_return(mock_shellout)
715
+ provider.should_receive(:read_user_info)
716
+ provider.should_receive(:dscl_set)
717
+ provider.should_receive(:sleep).with(3)
718
+ provider.should_receive(:shell_out).with("dscacheutil '-flushcache'")
719
+ provider.should_receive(:save_user_info)
720
+ provider.set_password
356
721
  end
722
+ end
723
+
724
+ describe "when the user does not yet exist and chef is creating it" do
725
+ context "with a numeric gid" do
726
+ before do
727
+ new_resource.comment "#mockssuck"
728
+ new_resource.gid 1001
729
+ end
730
+
731
+ 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
740
+ end
741
+
742
+ 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
745
+ end
746
+
747
+ 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
750
+ end
751
+
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
755
+ end
357
756
 
358
- it "should run safe_dscl with create /Users/user UserShell to set the users login shell" do
359
- @provider.should_receive(:safe_dscl).with("create /Users/toor UserShell '/usr/bin/false'").and_return(true)
360
- @provider.dscl_set_shell
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
760
+ end
361
761
  end
362
762
 
763
+ context "with a non-numeric gid" do
764
+ before do
765
+ new_resource.comment "#mockssuck"
766
+ new_resource.gid "newgroup"
767
+ end
768
+
769
+ 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
773
+ end
774
+
775
+ it "should raise an exception when the group does not exist" do
776
+ 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)
779
+ end
780
+ end
363
781
  end
364
782
 
365
783
  describe "when the user exists and chef is managing it" do
366
784
  before do
367
- @current_resource = @new_resource.dup
368
- @provider.current_resource = @current_resource
369
-
370
- # These are all different from @current_resource
371
- @new_resource.username "mud"
372
- @new_resource.uid 2342
373
- @new_resource.gid 2342
374
- @new_resource.home '/Users/death'
375
- @new_resource.password 'goaway'
376
- end
377
-
785
+ current_resource = new_resource.dup
786
+ provider.current_resource = current_resource
787
+
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'
794
+ end
795
+
378
796
  it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do
379
- @provider.should_receive :dscl_create_user
380
- @provider.should_receive :dscl_create_comment
381
- @provider.should_receive :set_uid
382
- @provider.should_receive :dscl_set_gid
383
- @provider.should_receive :modify_home
384
- @provider.should_receive :dscl_set_shell
385
- @provider.should_receive :modify_password
386
- @provider.create_user
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
387
805
  end
388
806
  end
389
807
 
390
808
  describe "when changing the gid" do
391
809
  before do
392
- @current_resource = @new_resource.dup
393
- @provider.current_resource = @current_resource
394
-
395
- # This is different from @current_resource
396
- @new_resource.gid 2342
810
+ current_resource = new_resource.dup
811
+ provider.current_resource = current_resource
812
+
813
+ # This is different from current_resource
814
+ new_resource.gid 2342
397
815
  end
398
-
816
+
399
817
  it "sets the gid" do
400
- @provider.should_receive :dscl_set_gid
401
- @provider.manage_user
818
+ provider.should_receive :dscl_set_gid
819
+ provider.manage_user
402
820
  end
403
821
  end
404
822
 
405
- describe "when the user exists and chef is removing it" do
406
- it "removes the user's home directory when the resource is configured to manage home" do
407
- @new_resource.supports({ :manage_home => true })
408
- @provider.should_receive(:safe_dscl).with("read /Users/toor").and_return("NFSHomeDirectory: /Users/fuuuuuuuuuuuuu")
409
- @provider.should_receive(:safe_dscl).with("delete /Users/toor")
410
- FileUtils.should_receive(:rm_rf).with("/Users/fuuuuuuuuuuuuu")
411
- @provider.remove_user
412
- end
413
-
414
- it "removes the user from any group memberships" do
415
- Etc.stub(:group).and_yield(OpenStruct.new(:name => 'ragefisters', :mem => 'toor'))
416
- @provider.should_receive(:safe_dscl).with("delete /Users/toor")
417
- @provider.should_receive(:safe_dscl).with("delete /Groups/ragefisters GroupMembership 'toor'")
418
- @provider.remove_user
823
+ describe "when the user exists" do
824
+ before do
825
+ provider.should_receive(:shell_out).with("plutil -convert xml1 -o - /var/db/dslocal/nodes/Default/users/toor.plist") do
826
+ ShellCmdResult.new(File.read(File.join(CHEF_SPEC_DATA, "mac_users/10.9.plist.xml")), "", 0)
827
+ end
828
+ provider.load_current_resource
829
+ end
830
+
831
+ describe "when Chef is removing the user" do
832
+ it "removes the user from the groups and deletes home directory when the resource is configured to manage home" do
833
+ new_resource.supports({ :manage_home => true })
834
+ provider.should_receive(:run_dscl).with("list /Groups").and_return("my_group\nyour_group\nreal_group\n")
835
+ provider.should_receive(:run_dscl).with("read /Groups/my_group").and_raise(Chef::Exceptions::DsclCommandFailed) # Empty group
836
+ provider.should_receive(:run_dscl).with("read /Groups/your_group").and_return("GroupMembership: not_you")
837
+ provider.should_receive(:run_dscl).with("read /Groups/real_group").and_return("GroupMembership: toor")
838
+ provider.should_receive(:run_dscl).with("delete /Groups/real_group GroupMembership 'toor'")
839
+ provider.should_receive(:run_dscl).with("delete /Users/toor")
840
+ FileUtils.should_receive(:rm_rf).with("/Users/vagrant")
841
+ provider.remove_user
842
+ end
419
843
  end
420
- end
421
844
 
422
- describe "when discovering if a user is locked" do
423
-
424
- it "determines the user is not locked when dscl shows an AuthenticationAuthority without a DisabledUser field" do
425
- @provider.should_receive(:safe_dscl).with("read /Users/toor")
426
- @provider.should_not be_locked
845
+ describe "when user is not locked" do
846
+ it "determines the user as not locked" do
847
+ provider.should_not be_locked
848
+ end
427
849
  end
428
850
 
429
- it "determines the user is locked when dscl shows an AuthenticationAuthority with a DisabledUser field" do
430
- @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\nAuthenticationAuthority: ;DisabledUser;\n")
431
- @provider.should be_locked
432
- end
851
+ describe "when user is locked" do
852
+ before do
853
+ auth_authority = provider.instance_variable_get(:@authentication_authority)
854
+ provider.instance_variable_set(:@authentication_authority, auth_authority + ";DisabledUser;")
855
+ end
856
+
857
+ it "determines the user as not locked" do
858
+ provider.should be_locked
859
+ end
433
860
 
434
- it "determines the user is not locked when dscl shows no AuthenticationAuthority" do
435
- @provider.should_receive(:safe_dscl).with('read /Users/toor').and_return("\n")
436
- @provider.should_not be_locked
861
+ it "can unlock the user" do
862
+ provider.should_receive(:run_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2>'")
863
+ provider.unlock_user
864
+ end
437
865
  end
438
866
  end
439
867
 
440
868
  describe "when locking the user" do
441
- it "should run safe_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
442
- @provider.should_receive(:safe_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'")
443
- @provider.lock_user
869
+ it "should run run_dscl with append /Users/user AuthenticationAuthority ;DisabledUser; to lock the user account" do
870
+ provider.should_receive(:run_dscl).with("append /Users/toor AuthenticationAuthority ';DisabledUser;'")
871
+ provider.lock_user
444
872
  end
445
873
  end
446
874
 
447
- describe "when unlocking the user" do
448
- it "removes DisabledUser from the authentication string" do
449
- @provider.should_receive(:safe_dscl).with("read /Users/toor AuthenticationAuthority").and_return("\nAuthenticationAuthority: ;ShadowHash; ;DisabledUser;\n")
450
- @provider.should_receive(:safe_dscl).with("create /Users/toor AuthenticationAuthority ';ShadowHash;'")
451
- @provider.unlock_user
452
- end
453
- end
454
875
  end