chef 11.14.2 → 11.14.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/chef/application.rb +16 -8
  3. data/lib/chef/dsl/recipe.rb +14 -0
  4. data/lib/chef/exceptions.rb +1 -0
  5. data/lib/chef/provider/env/windows.rb +5 -9
  6. data/lib/chef/provider/group/dscl.rb +27 -9
  7. data/lib/chef/provider/package/rpm.rb +2 -2
  8. data/lib/chef/provider/user/dscl.rb +544 -157
  9. data/lib/chef/resource.rb +3 -0
  10. data/lib/chef/resource/freebsd_package.rb +10 -2
  11. data/lib/chef/resource/user.rb +18 -0
  12. data/lib/chef/resource_reporter.rb +7 -7
  13. data/lib/chef/role.rb +2 -2
  14. data/lib/chef/version.rb +1 -1
  15. data/spec/data/mac_users/10.7-8.plist.xml +559 -0
  16. data/spec/data/mac_users/10.7-8.shadow.xml +11 -0
  17. data/spec/data/mac_users/10.7.plist.xml +559 -0
  18. data/spec/data/mac_users/10.7.shadow.xml +11 -0
  19. data/spec/data/mac_users/10.8.plist.xml +559 -0
  20. data/spec/data/mac_users/10.8.shadow.xml +21 -0
  21. data/spec/data/mac_users/10.9.plist.xml +560 -0
  22. data/spec/data/mac_users/10.9.shadow.xml +21 -0
  23. data/spec/functional/resource/env_spec.rb +137 -0
  24. data/spec/functional/resource/user/dscl_spec.rb +198 -0
  25. data/spec/functional/resource/{user_spec.rb → user/useradd_spec.rb} +1 -1
  26. data/spec/support/lib/chef/resource/zen_follower.rb +46 -0
  27. data/spec/unit/application_spec.rb +32 -9
  28. data/spec/unit/provider/group/dscl_spec.rb +38 -1
  29. data/spec/unit/provider/package/rpm_spec.rb +12 -0
  30. data/spec/unit/provider/user/dscl_spec.rb +659 -264
  31. data/spec/unit/provider/user/useradd_spec.rb +1 -0
  32. data/spec/unit/recipe_spec.rb +41 -0
  33. data/spec/unit/resource_reporter_spec.rb +48 -0
  34. data/spec/unit/resource_spec.rb +9 -2
  35. data/spec/unit/role_spec.rb +6 -0
  36. metadata +28 -3
@@ -240,6 +240,7 @@ describe Chef::Provider::Group::Dscl do
240
240
  @provider.load_current_resource
241
241
  @provider.define_resource_requirements
242
242
  end
243
+
243
244
  it "raises an error if the required binary /usr/bin/dscl doesn't exist" do
244
245
  File.should_receive(:exists?).with("/usr/bin/dscl").and_return(false)
245
246
 
@@ -251,7 +252,7 @@ describe Chef::Provider::Group::Dscl do
251
252
  lambda { @provider.process_resource_requirements }.should_not raise_error
252
253
  end
253
254
  end
254
-
255
+
255
256
  describe "when creating the group" do
256
257
  it "creates the group, password field, gid, and sets group membership" do
257
258
  @provider.should_receive(:set_gid).and_return(true)
@@ -294,3 +295,39 @@ describe Chef::Provider::Group::Dscl do
294
295
  end
295
296
  end
296
297
  end
298
+
299
+ describe 'Test DSCL loading' do
300
+ before do
301
+ @node = Chef::Node.new
302
+ @events = Chef::EventDispatch::Dispatcher.new
303
+ @run_context = Chef::RunContext.new(@node, {}, @events)
304
+ @new_resource = Chef::Resource::Group.new("aj")
305
+ @provider = Chef::Provider::Group::Dscl.new(@new_resource, @run_context)
306
+ @output = <<-EOF
307
+ AppleMetaNodeLocation: /Local/Default
308
+ Comment:
309
+ Test Group
310
+ GeneratedUID: AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA
311
+ NestedGroups: AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAB
312
+ Password: *
313
+ PrimaryGroupID: 999
314
+ RealName:
315
+ TestGroup
316
+ RecordName: com.apple.aj
317
+ RecordType: dsRecTypeStandard:Groups
318
+ GroupMembership: waka bar
319
+ EOF
320
+ @provider.stub(:safe_dscl).with("read /Groups/aj").and_return(@output)
321
+ @current_resource = @provider.load_current_resource
322
+
323
+ end
324
+
325
+ it 'should parse gid properly' do
326
+ File.stub(:exists?).and_return(true)
327
+ @current_resource.gid.should eq("999")
328
+ end
329
+ it 'should parse members properly' do
330
+ File.stub(:exists?).and_return(true)
331
+ @current_resource.members.should eq(['waka', 'bar'])
332
+ end
333
+ 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
@@ -20,142 +20,226 @@ 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
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
+ }
32
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)
56
135
  end
57
136
 
58
137
  it "raises an exception when dscl reports 'eDSRecordNotFound'" do
59
138
  shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
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)
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)
62
141
  end
63
142
  end
64
143
 
65
144
  describe "get_free_uid" do
66
145
  before do
67
- @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")
68
147
  end
69
148
 
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
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
73
157
  end
74
158
 
75
159
  it "should return the first unused uid number on or above 200" do
76
- @provider.get_free_uid.should == 202
160
+ provider.get_free_uid.should eq(502)
77
161
  end
78
162
 
79
163
  it "should raise an exception when the search limit is exhausted" do
80
164
  search_limit = 1
81
- lambda { @provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
165
+ lambda { provider.get_free_uid(search_limit) }.should raise_error(RuntimeError)
82
166
  end
83
167
  end
84
168
 
85
169
  describe "uid_used?" do
86
- before do
87
- @provider.stub(:safe_dscl).and_return("\naj 500\n")
88
- end
89
-
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)
170
+ it "should return false if not given any valid uid number" do
171
+ provider.uid_used?(nil).should be_false
93
172
  end
94
173
 
95
- it "should return true for a used uid number" do
96
- @provider.uid_used?(500).should be_true
97
- 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
98
178
 
99
- it "should return false for an unused uid number" do
100
- @provider.uid_used?(501).should be_false
101
- end
179
+ it "should return true for a used uid number" do
180
+ provider.uid_used?(500).should be_true
181
+ end
102
182
 
103
- it "should return false if not given any valid uid number" do
104
- @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
105
186
  end
106
187
  end
107
188
 
108
189
  describe "when determining the uid to set" do
109
190
  it "raises RequestedUIDUnavailable if the requested uid is already in use" do
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)
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)
113
194
  end
114
195
 
115
196
  it "finds a valid, unused uid when none is specified" do
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
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)
121
202
  end
122
203
 
123
204
  it "sets the uid specified in the resource" do
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
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
128
209
  end
129
210
  end
130
211
 
131
212
  describe "when modifying the home directory" do
213
+ let(:current_resource) {
214
+ new_resource.dup
215
+ }
216
+
132
217
  before do
133
- @new_resource.supports({ :manage_home => true })
134
- @new_resource.home('/Users/toor')
218
+ new_resource.supports({ :manage_home => true })
219
+ new_resource.home('/Users/toor')
135
220
 
136
- @current_resource = @new_resource.dup
137
- @provider.current_resource = @current_resource
221
+ provider.current_resource = current_resource
138
222
  end
139
223
 
140
224
  it "deletes the home directory when resource#home is nil" do
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
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
144
228
  end
145
229
 
146
230
 
147
231
  it "raises InvalidHomeDirectory when the resource's home directory doesn't look right" do
148
- @new_resource.home('epic-fail')
149
- 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)
150
234
  end
151
235
 
152
236
  it "moves the users home to the new location if it exists and the target location is different" do
153
- @new_resource.supports(:manage_home => true)
237
+ new_resource.supports(:manage_home => true)
154
238
 
155
239
  current_home = CHEF_SPEC_DATA + '/old_home_dir'
156
240
  current_home_files = [current_home + '/my-dot-emacs', current_home + '/my-dot-vim']
157
- @current_resource.home(current_home)
158
- @new_resource.gid(23)
241
+ current_resource.home(current_home)
242
+ new_resource.gid(23)
159
243
  ::File.stub(:exists?).with('/old/home/toor').and_return(true)
160
244
  ::File.stub(:exists?).with('/Users/toor').and_return(true)
161
245
 
@@ -165,316 +249,627 @@ describe Chef::Provider::User::Dscl do
165
249
  FileUtils.should_receive(:mv).with(current_home_files, "/Users/toor", :force => true)
166
250
  FileUtils.should_receive(:chown_R).with('toor','23','/Users/toor')
167
251
 
168
- @provider.should_receive(:safe_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
169
- @provider.modify_home
252
+ provider.should_receive(:run_dscl).with("create /Users/toor NFSHomeDirectory '/Users/toor'")
253
+ provider.dscl_set_home
170
254
  end
171
255
 
172
256
  it "should raise an exception when the systems user template dir (skel) cannot be found" do
173
257
  ::File.stub(:exists?).and_return(false,false,false)
174
- lambda { @provider.modify_home }.should raise_error(Chef::Exceptions::User)
258
+ lambda { provider.dscl_set_home }.should raise_error(Chef::Exceptions::User)
175
259
  end
176
260
 
177
261
  it "should run ditto to copy any missing files from skel to the new home dir" do
178
262
  ::File.should_receive(:exists?).with("/System/Library/User\ Template/English.lproj").and_return(true)
179
263
  FileUtils.should_receive(:chown_R).with('toor', '', '/Users/toor')
180
- @provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
181
- @provider.ditto_home
264
+ provider.should_receive(:shell_out!).with("ditto '/System/Library/User Template/English.lproj' '/Users/toor'")
265
+ provider.ditto_home
182
266
  end
183
267
 
184
268
  it "creates the user's NFSHomeDirectory and home directory" do
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
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
188
272
  end
189
273
  end
190
274
 
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)
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)
194
282
  end
195
283
 
196
- it "should return false otherwise" do
197
- @provider.osx_shadow_hash?("any other string").should eql(false)
284
+ def run_requirements
285
+ provider.define_resource_requirements
286
+ provider.action = :create
287
+ provider.process_resource_requirements
198
288
  end
199
- end
200
289
 
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)
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
205
296
  end
206
- end
207
297
 
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
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
314
+ end
315
+
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
336
+ end
337
+
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
213
371
  end
214
372
  end
215
373
 
216
- describe "shadow_hash_set?" 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
+
378
+ before do
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
217
386
 
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?
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
221
390
  end
222
391
 
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
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
227
395
  end
228
396
 
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
397
+ it "should set @user_exists" do
398
+ provider.load_current_resource
399
+ provider.instance_variable_get(:@user_exists).should be_false
232
400
  end
233
401
 
402
+ it "should set username" do
403
+ provider.load_current_resource
404
+ provider.current_resource.username.should eq("toor")
405
+ end
234
406
  end
235
407
 
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)
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
240
609
  end
241
610
  end
242
611
  end
243
612
 
244
- describe "when setting or modifying the user password" do
245
- before do
246
- @new_resource.password("password")
247
- @output = StringIO.new
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
248
616
  end
249
617
 
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}$/)
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
317
621
  end
318
622
  end
319
623
 
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)
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
324
627
  end
325
628
 
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
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
632
+ end
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
663
+ end
664
+
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
702
+ end
703
+ end
704
+
705
+ describe "set_password" do
706
+ before do
707
+ new_resource.password("something")
708
+ end
709
+
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
329
721
  end
330
722
  end
331
723
 
332
724
  describe "when the user does not yet exist and chef is creating it" do
333
725
  context "with a numeric gid" do
334
726
  before do
335
- @new_resource.comment "#mockssuck"
336
- @new_resource.gid 1001
727
+ new_resource.comment "#mockssuck"
728
+ new_resource.gid 1001
337
729
  end
338
730
 
339
731
  it "creates the user, comment field, sets uid, gid, configures the home directory, sets the shell, and sets the password" do
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
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
348
740
  end
349
741
 
350
742
  it "creates the user and sets the comment field" do
351
- @provider.should_receive(:safe_dscl).with("create /Users/toor").and_return(true)
352
- @provider.dscl_create_user
743
+ provider.should_receive(:run_dscl).with("create /Users/toor").and_return(true)
744
+ provider.dscl_create_user
353
745
  end
354
746
 
355
747
  it "sets the comment field" do
356
- @provider.should_receive(:safe_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
357
- @provider.dscl_create_comment
748
+ provider.should_receive(:run_dscl).with("create /Users/toor RealName '#mockssuck'").and_return(true)
749
+ provider.dscl_create_comment
358
750
  end
359
751
 
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
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
363
755
  end
364
756
 
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
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
368
760
  end
369
761
  end
370
762
 
371
763
  context "with a non-numeric gid" do
372
764
  before do
373
- @new_resource.comment "#mockssuck"
374
- @new_resource.gid "newgroup"
765
+ new_resource.comment "#mockssuck"
766
+ new_resource.gid "newgroup"
375
767
  end
376
768
 
377
769
  it "should map the group name to a numeric ID when the group exists" do
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
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
381
773
  end
382
774
 
383
775
  it "should raise an exception when the group does not exist" do
384
776
  shell_return = ShellCmdResult.new("<dscl_cmd> DS Error: -14136 (eDSRecordNotFound)", 'err', -14136)
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)
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)
387
779
  end
388
780
  end
389
781
  end
390
782
 
391
783
  describe "when the user exists and chef is managing it" do
392
784
  before do
393
- @current_resource = @new_resource.dup
394
- @provider.current_resource = @current_resource
785
+ current_resource = new_resource.dup
786
+ provider.current_resource = current_resource
395
787
 
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'
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'
402
794
  end
403
795
 
404
796
  it "sets the user, comment field, uid, gid, moves the home directory, sets the shell, and sets the password" do
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
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
413
805
  end
414
806
  end
415
807
 
416
808
  describe "when changing the gid" do
417
809
  before do
418
- @current_resource = @new_resource.dup
419
- @provider.current_resource = @current_resource
810
+ current_resource = new_resource.dup
811
+ provider.current_resource = current_resource
420
812
 
421
- # This is different from @current_resource
422
- @new_resource.gid 2342
813
+ # This is different from current_resource
814
+ new_resource.gid 2342
423
815
  end
424
816
 
425
817
  it "sets the gid" do
426
- @provider.should_receive :dscl_set_gid
427
- @provider.manage_user
818
+ provider.should_receive :dscl_set_gid
819
+ provider.manage_user
428
820
  end
429
821
  end
430
822
 
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
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
438
843
  end
439
844
 
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
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
445
849
  end
446
- end
447
-
448
- describe "when discovering if a user is locked" do
449
850
 
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
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
454
856
 
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
857
+ it "determines the user as not locked" do
858
+ provider.should be_locked
859
+ end
459
860
 
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
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
463
865
  end
464
866
  end
465
867
 
466
868
  describe "when locking the user" do
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
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
470
872
  end
471
873
  end
472
874
 
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
480
875
  end