summaryrefslogtreecommitdiff
path: root/spec/support/shared/functional/securable_resource.rb
blob: 2eeb16c78463132bcc272ef33b94a8d62a55e5d4 (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
#
# Author:: Seth Chisamore (<schisamo@opscode.com>)
# Author:: Mark Mzyk (<mmzyk@opscode.com>)
# Author:: John Keiser (<jkeiser@opscode.com>)
# Copyright:: Copyright (c) 2011 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.
#

# TODO test that these work when you are logged on as a user joined to a domain (rather than local computer)
# TODO test that you can set users from other domains

require 'etc'

shared_context "setup correct permissions" do
  context "on unix", :unix_only do
    context "with root", :requires_root do
      before :each do
        File.chown(Etc.getpwnam('nobody').uid, 1337, path)
        File.chmod(0776, path)
      end
    end

    context "without root", :requires_unprivileged_user do
      before :each do
        File.chmod(0776, path)
      end
    end
  end

  # FIXME: windows
end

shared_context "setup broken permissions" do
  context "on unix", :unix_only do
    context "with root", :requires_root do
      before :each do
        File.chown(0, 0, path)
        File.chmod(0644, path)
      end
    end
  
    context "without root", :requires_unprivileged_user do
      before :each do
        File.chmod(0644, path)
      end
    end
  end

  # FIXME: windows
end

shared_examples_for "a securable resource" do
  context "on Unix", :unix_only do
    let(:expected_user_name) { 'nobody' }
    let(:expected_uid) { Etc.getpwnam(expected_user_name).uid }
    let(:desired_gid) { 1337 }
    let(:expected_gid) { 1337 }

    pending "should set an owner (Rerun specs under root)", :requires_unprivileged_user => true
    pending "should set a group (Rerun specs under root)",  :requires_unprivileged_user => true

    it "should set an owner", :requires_root do
      resource.owner expected_user_name
      resource.run_action(:create)
      File.lstat(path).uid.should == expected_uid
    end

    it "should set a group", :requires_root do
      resource.group desired_gid
      resource.run_action(:create)
      File.lstat(path).gid.should == expected_gid
    end

    it "should set permissions in string form as an octal number" do
      mode_string = '776'
      resource.mode mode_string
      resource.run_action(:create)
      pending('Linux does not support lchmod', :if => resource.instance_of?(Chef::Resource::Link) && !os_x? && !freebsd?) do
        (File.lstat(path).mode & 007777).should == (mode_string.oct & 007777)
      end
    end

    it "should set permissions in numeric form as a ruby-interpreted octal" do
      mode_integer = 0776
      resource.mode mode_integer
      resource.run_action(:create)
      pending('Linux does not support lchmod', :if => resource.instance_of?(Chef::Resource::Link) && !os_x? && !freebsd?) do
        (File.lstat(path).mode & 007777).should == (mode_integer & 007777)
      end
    end
  end

  context "on Windows", :windows_only do

    if windows?
      SID = Chef::ReservedNames::Win32::Security::SID
      ACE = Chef::ReservedNames::Win32::Security::ACE
    end

    def get_security_descriptor(path)
      Chef::ReservedNames::Win32::Security.get_named_security_info(path)
    end

    def explicit_aces
      descriptor.dacl.select { |ace| ace.explicit? }
    end

    def extract_ace_properties(aces)
      hashes = []
        aces.each do |ace|
          hashes << { :mask => ace.mask, :type => ace.type, :flags => ace.flags }
        end
      hashes
    end

    # Standard expected rights
    let(:expected_read_perms) do
      {
        :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ,
        :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ,
      }
    end

    let(:expected_read_execute_perms) do
      {
        :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE,
        :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE
      }
    end

    let(:expected_write_perms) do
      {
        :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE,
        :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE
      }
    end

    let(:expected_modify_perms) do
      {
        :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_READ | Chef::ReservedNames::Win32::API::Security::GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE,
        :specific => Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_READ | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_WRITE | Chef::ReservedNames::Win32::API::Security::FILE_GENERIC_EXECUTE | Chef::ReservedNames::Win32::API::Security::DELETE
      }
    end

    let(:expected_full_control_perms) do
      {
        :generic => Chef::ReservedNames::Win32::API::Security::GENERIC_ALL,
        :specific => Chef::ReservedNames::Win32::API::Security::FILE_ALL_ACCESS
      }
    end

    RSpec::Matchers.define :have_expected_properties do |mask, type, flags|
      match do |ace|
        ace.mask == mask
        ace.type == type
        ace.flags == flags
      end
    end

    def descriptor
      get_security_descriptor(path)
    end

    before(:each) do
      resource.run_action(:delete)
    end

    it "sets owner to Administrators on create if owner is not specified" do
      File.exist?(path).should == false
      resource.run_action(:create)
      descriptor.owner.should == SID.Administrators
    end

    it "sets owner when owner is specified" do
      resource.owner 'Guest'
      resource.run_action(:create)
      descriptor.owner.should == SID.Guest
    end

    it "fails to set owner when owner has invalid characters" do
      lambda { resource.owner 'Lance "The Nose" Glindenberry III' }.should raise_error#(Chef::Exceptions::ValidationFailed)
    end

    it "sets owner when owner is specified with a \\" do
      resource.owner "#{ENV['USERDOMAIN']}\\Guest"
      resource.run_action(:create)
      descriptor.owner.should == SID.Guest
    end

    it "leaves owner alone if owner is not specified and resource already exists" do
      # Set owner to Guest so it's not the same as the current user (which is the default on create)
      resource.owner 'Guest'
      resource.run_action(:create)
      descriptor.owner.should == SID.Guest

      new_resource = create_resource
      new_resource.owner.should == nil
      new_resource.run_action(:create)
      descriptor.owner.should == SID.Guest
    end

    it "sets group to None on create if group is not specified" do
      resource.group.should == nil
      File.exist?(path).should == false
      resource.run_action(:create)
      descriptor.group.should == SID.None
    end

    it "sets group when group is specified" do
      resource.group 'Everyone'
      resource.run_action(:create)
      descriptor.group.should == SID.Everyone
    end

    it "fails to set group when group has invalid characters" do
      lambda { resource.group 'Lance "The Nose" Glindenberry III' }.should raise_error(Chef::Exceptions::ValidationFailed)
    end

    it "sets group when group is specified with a \\" do
      pending "Need to find a group containing a backslash that is on most peoples' machines" do
        resource.group "#{ENV['COMPUTERNAME']}\\Administrators"
        resource.run_action(:create)
        descriptor.group.should == SID.Everyone
      end
    end

    it "leaves group alone if group is not specified and resource already exists" do
      # Set group to Everyone so it's not the default (None)
      resource.group 'Everyone'
      resource.run_action(:create)
      descriptor.group.should == SID.Everyone

      new_resource = create_resource
      new_resource.group.should == nil
      new_resource.run_action(:create)
      descriptor.group.should == SID.Everyone
    end

    describe "with rights and deny_rights attributes" do

      it "correctly sets :read rights" do
        resource.rights(:read, 'Guest')
        resource.run_action(:create)
        explicit_aces.should == allowed_acl(SID.Guest, expected_read_perms)
      end

      it "correctly sets :read_execute rights" do
        resource.rights(:read_execute, 'Guest')
        resource.run_action(:create)
        explicit_aces.should == allowed_acl(SID.Guest, expected_read_execute_perms)
      end

      it "correctly sets :write rights" do
        resource.rights(:write, 'Guest')
        resource.run_action(:create)
        explicit_aces.should == allowed_acl(SID.Guest, expected_write_perms)
      end

      it "correctly sets :modify rights" do
        resource.rights(:modify, 'Guest')
        resource.run_action(:create)
        explicit_aces.should == allowed_acl(SID.Guest, expected_modify_perms)
      end

      it "correctly sets :full_control rights" do
        resource.rights(:full_control, 'Guest')
        resource.run_action(:create)
        explicit_aces.should == allowed_acl(SID.Guest, expected_full_control_perms)
      end

      it "correctly sets deny_rights" do
        # deny is an ACE with full rights, but is a deny type ace, not an allow type
        resource.deny_rights(:full_control, 'Guest')
        resource.run_action(:create)
        explicit_aces.should == denied_acl(SID.Guest, expected_full_control_perms)
      end

      it "Sets multiple rights" do
        resource.rights(:read, 'Everyone')
        resource.rights(:modify, 'Guest')
        resource.run_action(:create)

        explicit_aces.should ==
          allowed_acl(SID.Everyone, expected_read_perms) +
          allowed_acl(SID.Guest, expected_modify_perms)
      end

      it "Sets deny_rights ahead of rights" do
        resource.rights(:read, 'Everyone')
        resource.deny_rights(:modify, 'Guest')
        resource.run_action(:create)

        explicit_aces.should ==
          denied_acl(SID.Guest, expected_modify_perms) +
          allowed_acl(SID.Everyone, expected_read_perms)
      end

      it "Sets deny_rights ahead of rights when specified in reverse order" do
        resource.deny_rights(:modify, 'Guest')
        resource.rights(:read, 'Everyone')
        resource.run_action(:create)

        explicit_aces.should ==
          denied_acl(SID.Guest, expected_modify_perms) +
          allowed_acl(SID.Everyone, expected_read_perms)
      end

    end

    context "with a mode attribute" do
      if windows?
        Security = Chef::ReservedNames::Win32::API::Security
      end

      it "respects mode in string form as an octal number" do
        #on windows, mode cannot modify owner and/or group permissons
        #unless the owner and/or group as appropriate is specified
        resource.mode '400'
        resource.owner 'Guest'
        resource.group 'Everyone'
        resource.run_action(:create)

        explicit_aces.should == [ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ) ]
      end

      it "respects mode in numeric form as a ruby-interpreted octal" do
        resource.mode 0700
        resource.owner 'Guest'
        resource.run_action(:create)

        explicit_aces.should == [ ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE) ]
      end

      it "respects the owner, group and everyone bits of mode" do
        resource.mode 0754
        resource.owner 'Guest'
        resource.group 'Administrators'
        resource.run_action(:create)

        explicit_aces.should == [
          ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_WRITE | Security::FILE_GENERIC_EXECUTE | Security::DELETE),
          ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_READ | Security::FILE_GENERIC_EXECUTE),
          ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_READ)
        ]
      end

      it "respects the individual read, write and execute bits of mode" do
        resource.mode 0421
        resource.owner 'Guest'
        resource.group 'Administrators'
        resource.run_action(:create)

        explicit_aces.should == [
          ACE.access_allowed(SID.Guest, Security::FILE_GENERIC_READ),
          ACE.access_allowed(SID.Administrators, Security::FILE_GENERIC_WRITE | Security::DELETE),
          ACE.access_allowed(SID.Everyone, Security::FILE_GENERIC_EXECUTE)
        ]
      end

      it 'warns when mode tries to set owner bits but owner is not specified' do
        @warn = []
        Chef::Log.stub!(:warn) { |msg| @warn << msg }

        resource.mode 0400
        resource.run_action(:create)

        @warn.include?("Mode 400 includes bits for the owner, but owner is not specified").should be_true
      end

      it 'warns when mode tries to set group bits but group is not specified' do
        @warn = []
        Chef::Log.stub!(:warn) { |msg| @warn << msg }

        resource.mode 0040
        resource.run_action(:create)

        @warn.include?("Mode 040 includes bits for the group, but group is not specified").should be_true
      end
    end

  end
end