summaryrefslogtreecommitdiff
path: root/spec/support/shared/unit/provider/useradd_based_user_provider.rb
blob: fc7c79ef7d7435b6ea62397e55e34f774626cb8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Daniel DeLeo (<dan@opscode.com>)
# Copyright:: Copyright (c) 2008, 2010, 2013 Opscode, Inc.
#
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

shared_examples_for "a useradd-based user provider" do |supported_useradd_options|
  before(:each) do
    @node = Chef::Node.new
    @events = Chef::EventDispatch::Dispatcher.new
    @run_context = Chef::RunContext.new(@node, {}, @events)

    @new_resource = Chef::Resource::User.new("adam", @run_context)
    @new_resource.comment "Adam Jacob"
    @new_resource.uid 1000
    @new_resource.gid 1000
    @new_resource.home "/home/adam"
    @new_resource.shell "/usr/bin/zsh"
    @new_resource.password "abracadabra"
    @new_resource.system false
    @new_resource.manage_home false
    @new_resource.force false
    @new_resource.non_unique false
    @current_resource = Chef::Resource::User.new("adam", @run_context)
    @current_resource.comment "Adam Jacob"
    @current_resource.uid 1000
    @current_resource.gid 1000
    @current_resource.home "/home/adam"
    @current_resource.shell "/usr/bin/zsh"
    @current_resource.password "abracadabra"
    @current_resource.system false
    @current_resource.manage_home false
    @current_resource.force false
    @current_resource.non_unique false
    @current_resource.supports({:manage_home => false, :non_unique => false})
  end

  describe "when setting option" do

    supported_useradd_options.each do |attribute, option|
      it "should check for differences in #{attribute} between the new and current resources" do
        expect(@current_resource).to receive(attribute)
        expect(@new_resource).to receive(attribute)
        provider.universal_options
      end

      it "should set the option for #{attribute} if the new resources #{attribute} is not nil" do
        allow(@new_resource).to receive(attribute).and_return("hola")
        expect(provider.universal_options).to eql([option, 'hola'])
      end

      it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management" do
        allow(@new_resource).to receive(:supports).and_return({:manage_home => false,
                                                    :non_unique => false})
        allow(@new_resource).to receive(attribute).and_return("hola")
        expect(provider.universal_options).to eql([option, 'hola'])
      end

      it "should set the option for #{attribute} if the new resources #{attribute} is not nil, without homedir management (using real attributes)" do
        allow(@new_resource).to receive(:manage_home).and_return(false)
        allow(@new_resource).to receive(:non_unique).and_return(false)
        allow(@new_resource).to receive(:non_unique).and_return(false)
        allow(@new_resource).to receive(attribute).and_return("hola")
        expect(provider.universal_options).to eql([option, 'hola'])
      end
    end

    it "should combine all the possible options" do
      combined_opts = []
      supported_useradd_options.sort{ |a,b| a[0] <=> b[0] }.each do |attribute, option|
        allow(@new_resource).to receive(attribute).and_return("hola")
        combined_opts << option << 'hola'
      end
      expect(provider.universal_options).to eql(combined_opts)
    end

    describe "when we want to create a system user" do
      before do
        @new_resource.manage_home(true)
        @new_resource.non_unique(false)
      end

      it "should set useradd -r" do
        @new_resource.system(true)
        expect(provider.useradd_options).to eq([ "-r" ])
      end
    end

    describe "when the resource has a different home directory and supports home directory management" do
      before do
        allow(@new_resource).to receive(:home).and_return("/wowaweea")
        allow(@new_resource).to receive(:supports).and_return({:manage_home => true,
                                                   :non_unique => false})
      end

      it "should set -m -d /homedir" do
        expect(provider.universal_options).to eq(%w[-d /wowaweea -m])
        expect(provider.useradd_options).to eq([])
      end
    end

    describe "when the resource has a different home directory and supports home directory management (using real attributes)" do
      before do
        allow(@new_resource).to receive(:home).and_return("/wowaweea")
        allow(@new_resource).to receive(:manage_home).and_return(true)
        allow(@new_resource).to receive(:non_unique).and_return(false)
      end

      it "should set -m -d /homedir" do
        expect(provider.universal_options).to eql(%w[-d /wowaweea -m])
        expect(provider.useradd_options).to eq([])
      end
    end

    describe "when the resource supports non_unique ids" do
      before do
        allow(@new_resource).to receive(:supports).and_return({:manage_home => false,
                                                  :non_unique => true})
      end

      it "should set -m -o" do
        expect(provider.universal_options).to eql([ "-o" ])
      end
    end

    describe "when the resource supports non_unique ids (using real attributes)" do
      before do
        allow(@new_resource).to receive(:manage_home).and_return(false)
        allow(@new_resource).to receive(:non_unique).and_return(true)
      end

      it "should set -m -o" do
        expect(provider.universal_options).to eql([ "-o" ])
      end
    end
  end

  describe "when creating a user" do
    before(:each) do
      @current_resource = Chef::Resource::User.new(@new_resource.name, @run_context)
      @current_resource.username(@new_resource.username)
      provider.current_resource = @current_resource
      provider.new_resource.manage_home true
      provider.new_resource.home "/Users/mud"
      provider.new_resource.gid '23'
    end

    it "runs useradd with the computed command options" do
      command = ["useradd",
                  "-c",  'Adam Jacob',
                  "-g", '23' ]
      command.concat(["-p", 'abracadabra']) if supported_useradd_options.key?("password")
      command.concat([ "-s", '/usr/bin/zsh',
                       "-u", '1000',
                       "-d", '/Users/mud',
                       "-m",
                       "adam" ])
      expect(provider).to receive(:shell_out!).with(*command).and_return(true)
      provider.create_user
    end

    describe "and home is not specified for new system user resource" do

      before do
        provider.new_resource.system true
        # there is no public API to set attribute's value to nil
        provider.new_resource.instance_variable_set("@home", nil)
      end

      it "should not include -m or -d in the command options" do
        command = ["useradd",
                    "-c", 'Adam Jacob',
                    "-g", '23']
        command.concat(["-p", 'abracadabra']) if supported_useradd_options.key?("password")
        command.concat([ "-s", '/usr/bin/zsh',
                         "-u", '1000',
                         "-r",
                         "adam" ])
        expect(provider).to receive(:shell_out!).with(*command).and_return(true)
        provider.create_user
      end

    end

  end

  describe "when managing a user" do
    before(:each) do
      provider.new_resource.manage_home true
      provider.new_resource.home "/Users/mud"
      provider.new_resource.gid '23'
    end

    # CHEF-3423, -m must come before the username
    # CHEF-4305, -d must come before -m to support CentOS/RHEL 5
    it "runs usermod with the computed command options" do
      command = ["usermod",
                  "-g", '23',
                  "-d", '/Users/mud',
                  "-m",
                  "adam" ]
      expect(provider).to receive(:shell_out!).with(*command).and_return(true)
      provider.manage_user
    end

    it "does not set the -r option to usermod" do
      @new_resource.system(true)
      command = ["usermod",
                  "-g", '23',
                  "-d", '/Users/mud',
                  "-m",
                  "adam" ]
      expect(provider).to receive(:shell_out!).with(*command).and_return(true)
      provider.manage_user
    end

    it "CHEF-3429: does not set -m if we aren't changing the home directory" do
      expect(provider).to receive(:updating_home?).and_return(false)
      command = ["usermod",
                  "-g", '23',
                  "adam" ]
      expect(provider).to receive(:shell_out!).with(*command).and_return(true)
      provider.manage_user
    end
  end

  describe "when removing a user" do

    it "should run userdel with the new resources user name" do
      expect(provider).to receive(:shell_out!).with("userdel", @new_resource.username).and_return(true)
      provider.remove_user
    end

    it "should run userdel with the new resources user name and -r if manage_home is true" do
      @new_resource.supports({ :manage_home => true,
                               :non_unique => false})
      expect(provider).to receive(:shell_out!).with("userdel", "-r", @new_resource.username).and_return(true)
      provider.remove_user
    end

    it "should run userdel with the new resources user name if non_unique is true" do
      @new_resource.supports({ :manage_home => false,
                               :non_unique => true})
      expect(provider).to receive(:shell_out!).with("userdel", @new_resource.username).and_return(true)
      provider.remove_user
    end

    it "should run userdel with the new resources user name and -f if force is true" do
      @new_resource.force(true)
      expect(provider).to receive(:shell_out!).with("userdel", "-f", @new_resource.username).and_return(true)
      provider.remove_user
    end
  end

  describe "when checking the lock" do
    # lazy initialize so we can modify stdout and stderr strings
    let(:passwd_s_status) do
      double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => @stdout, :stderr => @stderr)
    end

    before(:each) do
      # @node = Chef::Node.new
      # @new_resource = double("Chef::Resource::User",
      #   :nil_object => true,
      #   :username => "adam"
      # )
      #provider = Chef::Provider::User::Useradd.new(@node, @new_resource)
      @stdout = "root P 09/02/2008 0 99999 7 -1"
      @stderr = ""
    end

    it "should return false if status begins with P" do
      expect(provider).to receive(:shell_out!).
        with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
        and_return(passwd_s_status)
      expect(provider.check_lock).to eql(false)
    end

    it "should return false if status begins with N" do
      @stdout = "root N"
      expect(provider).to receive(:shell_out!).
        with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
        and_return(passwd_s_status)
      expect(provider.check_lock).to eql(false)
    end

    it "should return true if status begins with L" do
      @stdout = "root L"
      expect(provider).to receive(:shell_out!).
        with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
        and_return(passwd_s_status)
      expect(provider.check_lock).to eql(true)
    end

    it "should raise a Chef::Exceptions::User if passwd -S fails on anything other than redhat/centos" do
      @node.automatic_attrs[:platform] = 'ubuntu'
      expect(provider).to receive(:shell_out!).
        with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
        and_return(passwd_s_status)
      expect(passwd_s_status).to receive(:exitstatus).and_return(1)
      expect { provider.check_lock }.to raise_error(Chef::Exceptions::User)
    end

    ['redhat', 'centos'].each do |os|
      it "should not raise a Chef::Exceptions::User if passwd -S exits with 1 on #{os} and the passwd package is version 0.73-1" do
        @node.automatic_attrs[:platform] = os
        expect(passwd_s_status).to receive(:exitstatus).and_return(1)
        expect(provider).to receive(:shell_out!).
          with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
          and_return(passwd_s_status)
        rpm_status = double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => "passwd-0.73-1\n", :stderr => "")
        expect(provider).to receive(:shell_out!).with("rpm -q passwd").and_return(rpm_status)
        expect { provider.check_lock }.not_to raise_error
      end

      it "should raise a Chef::Exceptions::User if passwd -S exits with 1 on #{os} and the passwd package is not version 0.73-1" do
        @node.automatic_attrs[:platform] = os
        expect(passwd_s_status).to receive(:exitstatus).and_return(1)
        expect(provider).to receive(:shell_out!).
          with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
          and_return(passwd_s_status)
        rpm_status = double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => "passwd-0.73-2\n", :stderr => "")
        expect(provider).to receive(:shell_out!).with("rpm -q passwd").and_return(rpm_status)
        expect { provider.check_lock }.to raise_error(Chef::Exceptions::User)
      end

      it "should raise a ShellCommandFailed exception if passwd -S exits with something other than 0 or 1 on #{os}" do
        @node.automatic_attrs[:platform] = os
        expect(provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed)
        expect { provider.check_lock }.to raise_error(Mixlib::ShellOut::ShellCommandFailed)
      end
    end

    context "when in why run mode" do
      before do
        passwd_status = double("Mixlib::ShellOut command", :exitstatus => 0, :stdout => "", :stderr => "passwd: user 'chef-test' does not exist\n")
        expect(provider).to receive(:shell_out!).
          with("passwd", "-S", @new_resource.username, {:returns=>[0, 1]}).
          and_return(passwd_status)
        Chef::Config[:why_run] = true
      end

      it "should return false if the user does not exist" do
        expect(provider.check_lock).to eql(false)
      end

      it "should not raise an error if the user does not exist" do
        expect { provider.check_lock }.not_to raise_error
      end
    end
  end

  describe "when locking the user" do
    it "should run usermod -L with the new resources username" do
      expect(provider).to receive(:shell_out!).with("usermod", "-L", @new_resource.username)
      provider.lock_user
    end
  end

  describe "when unlocking the user" do
    it "should run usermod -L with the new resources username" do
      expect(provider).to receive(:shell_out!).with("usermod", "-U", @new_resource.username)
      provider.unlock_user
    end
  end

  describe "when checking if home needs updating" do
    [
     {
       "action" => "should return false if home matches",
       "current_resource_home" => [ "/home/laurent" ],
       "new_resource_home" => [ "/home/laurent" ],
       "expected_result" => false
     },
     {
       "action" => "should return true if home doesn't match",
       "current_resource_home" => [ "/home/laurent" ],
       "new_resource_home" => [ "/something/else" ],
       "expected_result" => true
     },
     {
       "action" => "should return false if home only differs by trailing slash",
       "current_resource_home" => [ "/home/laurent" ],
       "new_resource_home" => [ "/home/laurent/", "/home/laurent" ],
       "expected_result" => false
     },
     {
       "action" => "should return false if home is an equivalent path",
       "current_resource_home" => [ "/home/laurent" ],
       "new_resource_home" => [ "/home/./laurent", "/home/laurent" ],
       "expected_result" => false
     },
    ].each do |home_check|
      it home_check["action"] do
        provider.current_resource.home home_check["current_resource_home"].first
        @current_home_mock = double("Pathname")
        provider.new_resource.home home_check["new_resource_home"].first
        @new_home_mock = double("Pathname")

        expect(Pathname).to receive(:new).with(@current_resource.home).and_return(@current_home_mock)
        expect(@current_home_mock).to receive(:cleanpath).and_return(home_check["current_resource_home"].last)
        expect(Pathname).to receive(:new).with(@new_resource.home).and_return(@new_home_mock)
        expect(@new_home_mock).to receive(:cleanpath).and_return(home_check["new_resource_home"].last)

        expect(provider.updating_home?).to eq(home_check["expected_result"])
      end
    end
    it "should return true if the current home does not exist but a home is specified by the new resource" do
      @new_resource = Chef::Resource::User.new("adam", @run_context)
      @current_resource = Chef::Resource::User.new("adam", @run_context)
      provider = Chef::Provider::User::Useradd.new(@new_resource, @run_context)
      provider.current_resource = @current_resource
      @current_resource.home nil
      @new_resource.home "/home/kitten"

      expect(provider.updating_home?).to eq(true)
    end
  end
end