macadmin 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +163 -0
- data/Rakefile +23 -0
- data/ext/macadmin/password/crypto.c +89 -0
- data/ext/macadmin/password/extconf.rb +3 -0
- data/lib/macadmin.rb +22 -0
- data/lib/macadmin/common.rb +80 -0
- data/lib/macadmin/dslocal.rb +252 -0
- data/lib/macadmin/dslocal/computer.rb +22 -0
- data/lib/macadmin/dslocal/computergroup.rb +19 -0
- data/lib/macadmin/dslocal/dslocalnode.rb +281 -0
- data/lib/macadmin/dslocal/group.rb +82 -0
- data/lib/macadmin/dslocal/user.rb +113 -0
- data/lib/macadmin/mcx.rb +227 -0
- data/lib/macadmin/password.rb +72 -0
- data/lib/macadmin/shadowhash.rb +297 -0
- data/lib/macadmin/version.rb +8 -0
- data/macadmin.gemspec +35 -0
- data/spec/common_spec.rb +49 -0
- data/spec/computer_spec.rb +133 -0
- data/spec/computergroup_spec.rb +274 -0
- data/spec/dslocal_spec.rb +179 -0
- data/spec/dslocalnode_spec.rb +343 -0
- data/spec/group_spec.rb +275 -0
- data/spec/macadmin_spec.rb +13 -0
- data/spec/mcx_spec.rb +145 -0
- data/spec/password_spec.rb +40 -0
- data/spec/shadowhash_spec.rb +309 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/user_spec.rb +248 -0
- metadata +218 -0
@@ -0,0 +1,343 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MacAdmin::DSLocalNode do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
require 'etc'
|
7
|
+
this_user = Etc.getpwnam(Etc.getlogin)
|
8
|
+
this_uid = this_user.uid
|
9
|
+
this_gid = this_user.gid
|
10
|
+
# Create a dslocal sandbox
|
11
|
+
@test_dir = "/private/tmp/macadmin_dslocalnode_test.#{rand(100000)}"
|
12
|
+
@child_dirs = ['aliases', 'computer_lists', 'computergroups', 'computers', 'config', 'groups', 'networks', 'users']
|
13
|
+
MacAdmin::DSLocalNode.send(:remove_const, :SANDBOX_FILE)
|
14
|
+
MacAdmin::DSLocalNode.send(:remove_const, :DSLOCAL_ROOT)
|
15
|
+
MacAdmin::DSLocalNode.send(:remove_const, :PREFERENCES)
|
16
|
+
MacAdmin::DSLocalNode.send(:remove_const, :PREFERENCES_LEGACY)
|
17
|
+
MacAdmin::DSLocalNode.send(:remove_const, :OWNER)
|
18
|
+
MacAdmin::DSLocalNode.send(:remove_const, :GROUP)
|
19
|
+
MacAdmin::DSLocalNode::DSLOCAL_ROOT = File.expand_path @test_dir
|
20
|
+
MacAdmin::DSLocalNode::SANDBOX_FILE = File.expand_path "#{@test_dir}/opendirectoryd.sb"
|
21
|
+
MacAdmin::DSLocalNode::PREFERENCES = File.expand_path "#{@test_dir}/config.plist"
|
22
|
+
MacAdmin::DSLocalNode::PREFERENCES_LEGACY = File.expand_path "#{@test_dir}/legacy.plist"
|
23
|
+
MacAdmin::DSLocalNode::OWNER = this_uid
|
24
|
+
MacAdmin::DSLocalNode::GROUP = this_gid
|
25
|
+
@root = "#{@test_dir}/Default"
|
26
|
+
FileUtils.mkdir_p @root
|
27
|
+
begin
|
28
|
+
FileUtils.mkdir_p @root unless File.exist? @root
|
29
|
+
FileUtils.chmod(0700, @root)
|
30
|
+
@child_dirs.each do |child|
|
31
|
+
FileUtils.mkdir_p("#{@root}/#{child}") unless File.exist?("#{@root}/#{child}")
|
32
|
+
FileUtils.chmod(0700, "#{@root}/#{child}")
|
33
|
+
end
|
34
|
+
FileUtils.chown_R this_uid, this_gid, @root
|
35
|
+
rescue Exception => e
|
36
|
+
p e.message
|
37
|
+
end
|
38
|
+
|
39
|
+
@config = <<-CONFIG
|
40
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
41
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
42
|
+
<plist version="1.0">
|
43
|
+
<dict>
|
44
|
+
<key>comment</key>
|
45
|
+
<string>Default search policy</string>
|
46
|
+
<key>enabled</key>
|
47
|
+
<true/>
|
48
|
+
<key>mappings</key>
|
49
|
+
<dict/>
|
50
|
+
<key>modules</key>
|
51
|
+
<dict>
|
52
|
+
<key>session</key>
|
53
|
+
<array>
|
54
|
+
<dict>
|
55
|
+
<key>module</key>
|
56
|
+
<string>search</string>
|
57
|
+
<key>options</key>
|
58
|
+
<dict>
|
59
|
+
<key>dsAttrTypeStandard:CSPSearchPath</key>
|
60
|
+
<array>
|
61
|
+
<string>/Local/Default</string>
|
62
|
+
</array>
|
63
|
+
<key>dsAttrTypeStandard:LSPSearchPath</key>
|
64
|
+
<array>
|
65
|
+
<string>/Local/Default</string>
|
66
|
+
</array>
|
67
|
+
<key>dsAttrTypeStandard:NSPSearchPath</key>
|
68
|
+
<array>
|
69
|
+
<string>/Local/Default</string>
|
70
|
+
</array>
|
71
|
+
<key>dsAttrTypeStandard:SearchPolicy</key>
|
72
|
+
<string>dsAttrTypeStandard:NSPSearchPath</string>
|
73
|
+
<key>notify_of_changes</key>
|
74
|
+
<true/>
|
75
|
+
<key>requiredNodes</key>
|
76
|
+
<array>
|
77
|
+
<string>/Local/Default</string>
|
78
|
+
</array>
|
79
|
+
</dict>
|
80
|
+
<key>uuid</key>
|
81
|
+
<string>A840FC81-A6CD-4665-899E-F8B52B1C6EC4</string>
|
82
|
+
</dict>
|
83
|
+
</array>
|
84
|
+
</dict>
|
85
|
+
<key>node name</key>
|
86
|
+
<string>/Search</string>
|
87
|
+
</dict>
|
88
|
+
</plist>
|
89
|
+
CONFIG
|
90
|
+
|
91
|
+
@legacy = <<-CONFIG
|
92
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
93
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
94
|
+
<plist version="1.0">
|
95
|
+
<dict>
|
96
|
+
<key>DHCP LDAP</key>
|
97
|
+
<dict>
|
98
|
+
<key>/Sets/2A7FEC83-9B78-4BD1-A4E3-29F685EFD97A</key>
|
99
|
+
<false/>
|
100
|
+
</dict>
|
101
|
+
<key>Search Node PlugIn Version</key>
|
102
|
+
<string>Search Node PlugIn Version 1.7</string>
|
103
|
+
<key>Search Policy</key>
|
104
|
+
<integer>1</integer>
|
105
|
+
</dict>
|
106
|
+
</plist>
|
107
|
+
CONFIG
|
108
|
+
|
109
|
+
@sandbox = <<-SANDBOX
|
110
|
+
;; Copyright (c) 2011 Apple Inc. All Rights reserved.
|
111
|
+
;;
|
112
|
+
;; WARNING: The sandbox rules in this file currently constitute
|
113
|
+
;; Apple System Private Interface and are subject to change at any time and
|
114
|
+
;; without notice.
|
115
|
+
;;
|
116
|
+
|
117
|
+
(version 1)
|
118
|
+
|
119
|
+
(allow default)
|
120
|
+
|
121
|
+
;; deny lookup to coreservicesd to avoid deadlocks
|
122
|
+
(deny mach-lookup
|
123
|
+
(global-name "com.apple.CoreServices.coreservicesd")
|
124
|
+
(with no-log)
|
125
|
+
(with no-callout)
|
126
|
+
)
|
127
|
+
|
128
|
+
;; we don't allow inbound or bound ports
|
129
|
+
(deny network-inbound (with no-callout))
|
130
|
+
(deny network-bind (with no-callout))
|
131
|
+
|
132
|
+
(allow network-inbound (local udp))
|
133
|
+
(allow network-bind (local ip))
|
134
|
+
|
135
|
+
;; deny all file writes except those explicity allowed
|
136
|
+
(deny file-write* (with no-callout))
|
137
|
+
|
138
|
+
(allow process-exec (literal "/usr/bin/nsupdate"))
|
139
|
+
|
140
|
+
;; allow slapconfig to be launched without sandbox due to denial of file-write
|
141
|
+
(allow process-exec (literal "/usr/sbin/slapconfig") (with no-sandbox))
|
142
|
+
|
143
|
+
;; 10735867
|
144
|
+
(allow process-exec (literal "/usr/sbin/kextcache") (with no-sandbox))
|
145
|
+
|
146
|
+
;; restrict where we write
|
147
|
+
(allow file-write*
|
148
|
+
;; key OpenDirectory files
|
149
|
+
(mount-relative-regex
|
150
|
+
;; our database and shadowhash files
|
151
|
+
;; we might be targeting '/' or some other volume
|
152
|
+
#"^(/private)?/var/db/dslocal/nodes/Default(/|$)"
|
153
|
+
#"^(/private)?/var/db/dslocal/nodes/Earth(/|$)"
|
154
|
+
#"^(/private)?/var/db/dslocal/nodes/MCX(/|$)"
|
155
|
+
#"^(/private)?/var/db/shadow(/|$)"
|
156
|
+
)
|
157
|
+
|
158
|
+
(regex
|
159
|
+
;; configuration files
|
160
|
+
;; we ignore DirectoryService files because they are handled by dspluginhelperd
|
161
|
+
#"^/Library/Preferences/OpenDirectory"
|
162
|
+
|
163
|
+
;; our log files
|
164
|
+
#"^(/private)?/var/log/opendirectoryd.log"
|
165
|
+
|
166
|
+
;; configuration files
|
167
|
+
#"^/Library/Preferences/SystemConfiguration/"
|
168
|
+
)
|
169
|
+
|
170
|
+
;; our SystemCache related files
|
171
|
+
(regex
|
172
|
+
#"^(/private)?/etc/memberd.conf" ;;; for unlinking
|
173
|
+
)
|
174
|
+
|
175
|
+
;; update system keychain
|
176
|
+
(regex
|
177
|
+
#"^/Library/Keychains/System.keychain"
|
178
|
+
#"^/Library/Keychains/\."
|
179
|
+
#"^(/private)?/var/db/mds/system/mds.lock$"
|
180
|
+
)
|
181
|
+
|
182
|
+
;; kerberos keytab updates
|
183
|
+
(regex
|
184
|
+
#"^(/private)?/etc/krb5.keytab$"
|
185
|
+
#"^(/private)?/tmp/krb5cc_"
|
186
|
+
)
|
187
|
+
|
188
|
+
;; additional required
|
189
|
+
(literal "/dev/dtracehelper")
|
190
|
+
(literal "/dev/null")
|
191
|
+
(literal "/dev/random")
|
192
|
+
|
193
|
+
;; for NIS support
|
194
|
+
(regex
|
195
|
+
#"^(/private)?/var/yp/"
|
196
|
+
#"^(/private)?/etc/defaultdomain$"
|
197
|
+
)
|
198
|
+
)
|
199
|
+
|
200
|
+
SANDBOX
|
201
|
+
|
202
|
+
File.open(MacAdmin::DSLocalNode::PREFERENCES, 'w') { |f| f.write(@config) }
|
203
|
+
File.open(MacAdmin::DSLocalNode::PREFERENCES_LEGACY, 'w') { |f| f.write(@legacy) }
|
204
|
+
|
205
|
+
end
|
206
|
+
|
207
|
+
before :each do
|
208
|
+
File.open(MacAdmin::DSLocalNode::SANDBOX_FILE, 'w') { |f| f.write(@sandbox) }
|
209
|
+
end
|
210
|
+
|
211
|
+
after :each do
|
212
|
+
if subject.name.eql? 'Earth'
|
213
|
+
FileUtils.rm_rf subject.root if File.exists? subject.root
|
214
|
+
FileUtils.rm_rf MacAdmin::DSLocalNode::SANDBOX_FILE if File.exists? MacAdmin::DSLocalNode::SANDBOX_FILE
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#new' do
|
219
|
+
|
220
|
+
context 'when intialized without arguments it returns the Default node' do
|
221
|
+
subject { DSLocalNode.new }
|
222
|
+
it { should be_an_instance_of DSLocalNode }
|
223
|
+
its (:name) { should eq 'Default' }
|
224
|
+
its (:label) { should eq '/Local/Default' }
|
225
|
+
its (:root) { should eq "#{MacAdmin::DSLocalNode::DSLOCAL_ROOT}/Default" }
|
226
|
+
end
|
227
|
+
|
228
|
+
context 'when intialized with a name (String)' do
|
229
|
+
subject { DSLocalNode.new 'Earth' }
|
230
|
+
it { should be_an_instance_of DSLocalNode }
|
231
|
+
its (:name) { should eq 'Earth' }
|
232
|
+
its (:label) { should eq '/Local/Earth' }
|
233
|
+
its (:root) { should eq "#{MacAdmin::DSLocalNode::DSLOCAL_ROOT}/Earth" }
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
describe '#exists?' do
|
239
|
+
|
240
|
+
context 'when the defined directory structure exists' do
|
241
|
+
subject { DSLocalNode.new }
|
242
|
+
it { subject.exists?.should be_true }
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'when the defined directory structure does NOT exist' do
|
246
|
+
subject { DSLocalNode.new 'Earth' }
|
247
|
+
it { subject.exists?.should be_false }
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
describe '#active?' do
|
253
|
+
|
254
|
+
context "when the node is in the Directory Service's search path" do
|
255
|
+
subject { DSLocalNode.new }
|
256
|
+
it { subject.active?.should be_true }
|
257
|
+
end
|
258
|
+
|
259
|
+
context "when the node is NOT in the Directory Service's search path" do
|
260
|
+
subject { DSLocalNode.new 'Earth' }
|
261
|
+
it { subject.active?.should be_false }
|
262
|
+
end
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
describe '#create' do
|
267
|
+
|
268
|
+
subject { DSLocalNode.new 'Earth' }
|
269
|
+
it 'creates the defined directory structure' do
|
270
|
+
subject.create.should be_true
|
271
|
+
subject.exists?.should be_true
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
|
276
|
+
describe '#activate' do
|
277
|
+
|
278
|
+
subject { DSLocalNode.new 'Earth' }
|
279
|
+
it "add the node to Directory Service's search path" do
|
280
|
+
subject.activate.should be_true
|
281
|
+
subject.active?.should be_true
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
describe '#deactivate' do
|
287
|
+
|
288
|
+
subject { DSLocalNode.new 'Earth' }
|
289
|
+
it "removes the node from the Directory Service's search path" do
|
290
|
+
subject.activate
|
291
|
+
subject.deactivate.should be_true
|
292
|
+
subject.active?.should be_false
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
describe "#exists_and_active?" do
|
298
|
+
|
299
|
+
context "when the node exists and is active" do
|
300
|
+
subject { DSLocalNode.new }
|
301
|
+
it { subject.exists_and_active?.should be_true }
|
302
|
+
end
|
303
|
+
|
304
|
+
context "when the node exists but is NOT active" do
|
305
|
+
subject { DSLocalNode.new 'Earth' }
|
306
|
+
it 'should be_false' do
|
307
|
+
subject.create
|
308
|
+
subject.exists_and_active?.should be_false
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
context "when the node is active but does NOT exist" do
|
313
|
+
subject { DSLocalNode.new 'Earth' }
|
314
|
+
it 'should be_false' do
|
315
|
+
subject.activate
|
316
|
+
subject.exists_and_active?.should be_false
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
end
|
321
|
+
|
322
|
+
describe "#create_and_activate" do
|
323
|
+
subject { DSLocalNode.new 'Earth' }
|
324
|
+
it "should be_true" do
|
325
|
+
subject.create_and_activate
|
326
|
+
subject.exists_and_active?.should be_true
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
describe "#destroy_and_deactivate" do
|
331
|
+
subject { DSLocalNode.new 'Earth' }
|
332
|
+
it "should be_false" do
|
333
|
+
subject.create_and_activate
|
334
|
+
subject.destroy_and_deactivate
|
335
|
+
subject.exists_and_active?.should be_false
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
after :all do
|
340
|
+
FileUtils.rm_rf @test_dir
|
341
|
+
end
|
342
|
+
|
343
|
+
end
|
data/spec/group_spec.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe MacAdmin::Group do
|
4
|
+
|
5
|
+
before :all do
|
6
|
+
# Create a dslocal sandbox
|
7
|
+
@test_dir = "/private/tmp/macadmin_group_test.#{rand(100000)}"
|
8
|
+
MacAdmin::DSLocalRecord.send(:remove_const, :DSLOCAL_ROOT)
|
9
|
+
MacAdmin::DSLocalRecord::DSLOCAL_ROOT = File.expand_path @test_dir
|
10
|
+
FileUtils.mkdir_p "#{@test_dir}/Default/groups"
|
11
|
+
FileUtils.mkdir_p "#{@test_dir}/Default/users"
|
12
|
+
|
13
|
+
# Create two group record plists that we can load
|
14
|
+
foo = {
|
15
|
+
"gid" => ["501"],
|
16
|
+
"realname" => ["Foo"],
|
17
|
+
"name" => ["foo"],
|
18
|
+
"generateduid" => ["00000000-0000-0000-0000-000000000001"],
|
19
|
+
}
|
20
|
+
|
21
|
+
bar = {
|
22
|
+
"gid" => ["502"],
|
23
|
+
"realname" => ["Bar"],
|
24
|
+
"name" => ["bar"],
|
25
|
+
"generateduid" => ["00000000-0000-0000-0000-000000000002"],
|
26
|
+
}
|
27
|
+
|
28
|
+
[foo, bar].each do |group|
|
29
|
+
record = CFPropertyList::List.new
|
30
|
+
record.value = CFPropertyList.guess(group)
|
31
|
+
record.save("#{@test_dir}/Default/groups/#{group['name'].first}.plist", CFPropertyList::List::FORMAT_BINARY)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#new' do
|
36
|
+
|
37
|
+
context 'throws an ArgumentError when given fewer than 1 params' do
|
38
|
+
subject { Group.new }
|
39
|
+
it { expect { subject }.to raise_error(ArgumentError) }
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when created from a parameter Hash' do
|
43
|
+
subject { Group.new :name => 'baz', :gid => "666" }
|
44
|
+
it { should be_an_instance_of Group }
|
45
|
+
it 'should have a valid generateduid attribute' do
|
46
|
+
(UUID.valid?(subject.generateduid.first)).should be_true
|
47
|
+
end
|
48
|
+
its (:gid) { should eq ['666'] }
|
49
|
+
its (:name) { should eq ['baz'] }
|
50
|
+
its (:realname) { should eq ['baz'.capitalize] }
|
51
|
+
its (:users) { should eq [] }
|
52
|
+
its (:groupmembers) { should eq [] }
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'when created from a just a name (String)' do
|
56
|
+
subject { Group.new 'baz' }
|
57
|
+
it { should be_an_instance_of Group }
|
58
|
+
it 'should have a valid generateduid attribute' do
|
59
|
+
(UUID.valid?(subject.generateduid.first)).should be_true
|
60
|
+
end
|
61
|
+
its (:gid) { should eq ['503'] } # 503 should be the next available GID
|
62
|
+
its (:name) { should eq ['baz'] }
|
63
|
+
its (:realname) { should eq ['baz'.capitalize] }
|
64
|
+
its (:users) { should eq [] }
|
65
|
+
its (:groupmembers) { should eq [] }
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#exists?' do
|
71
|
+
|
72
|
+
context "when the object is different from its associated file" do
|
73
|
+
subject { Group.new :name => 'foo', :gid => "503" }
|
74
|
+
it 'should return false' do
|
75
|
+
subject.exists?.should be_false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "when ALL the object's attributes match its associated file" do
|
80
|
+
subject { Group.new :name => 'foo', :gid => "501" }
|
81
|
+
it 'should return true' do
|
82
|
+
subject.exists?.should be_true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the object does not have an associated file on disk" do
|
87
|
+
subject { Group.new :name => 'baz' }
|
88
|
+
it 'should return false' do
|
89
|
+
subject.exists?.should be_false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#create' do
|
96
|
+
|
97
|
+
context "with NO path parameter (default)" do
|
98
|
+
subject { Group.new :name => 'baz', :gid => "666" }
|
99
|
+
it 'saves the record to disk on the derived path' do
|
100
|
+
subject.create.should be_true
|
101
|
+
File.exists?(subject.file).should be_true
|
102
|
+
end
|
103
|
+
after do
|
104
|
+
FileUtils.rm_rf subject.file
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context "with a path parameter" do
|
109
|
+
path = "/private/tmp/group-create-method-test.plist"
|
110
|
+
subject { Group.new :name => 'baz', :gid => "666" }
|
111
|
+
it 'saves the record to disk on the path specified' do
|
112
|
+
subject.create(path).should be_true
|
113
|
+
end
|
114
|
+
after do
|
115
|
+
FileUtils.rm_rf path
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#destroy' do
|
122
|
+
subject { Group.new :name => 'baz', :gid => "666" }
|
123
|
+
it 'removes the record on disk and returns true' do
|
124
|
+
subject.create
|
125
|
+
subject.destroy.should be_true
|
126
|
+
File.exists?(subject.file).should be_false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#users=' do
|
131
|
+
subject { Group.new 'foo' }
|
132
|
+
before { subject.users = ['foo', 'bar'] }
|
133
|
+
it 'replaces the membership array for users' do
|
134
|
+
subject['users'].should eq ['foo', 'bar']
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
describe '#users' do
|
139
|
+
subject { Group.new 'foo' }
|
140
|
+
before { subject.users = ['foo', 'bar'] }
|
141
|
+
it 'returns a membership array of users' do
|
142
|
+
subject.users.should eq ['foo', 'bar']
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe '#has_user?' do
|
147
|
+
subject { Group.new 'baz' }
|
148
|
+
|
149
|
+
context 'when membership array is non-existent' do
|
150
|
+
it 'should be false' do
|
151
|
+
subject.has_user?('bender').should be_false
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context 'when membership array is populated' do
|
156
|
+
before { subject.users = ['fry', 'bender'] }
|
157
|
+
it 'should be true if the user is a member of the group' do
|
158
|
+
subject.has_user?('fry').should be_true
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'should be false if the user is NOT a member of the group' do
|
162
|
+
subject.has_user?('leela').should be_false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
describe '#add_user' do
|
169
|
+
subject { Group.new 'baz' }
|
170
|
+
|
171
|
+
context 'when the member does NOT exist' do
|
172
|
+
it 'should raise an error' do
|
173
|
+
expect { subject.add_user('invalid') }.to raise_error(StandardError)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'when the member exists' do
|
178
|
+
it "should add the user to the users array" do
|
179
|
+
fry = User.new 'fry'
|
180
|
+
fry.create
|
181
|
+
subject.add_user('fry').should be_nil
|
182
|
+
subject['users'].should eql ["fry"]
|
183
|
+
fry.destroy
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
describe '#rm_user' do
|
190
|
+
subject { Group.new 'foo' }
|
191
|
+
|
192
|
+
context 'when user does NOT exist' do
|
193
|
+
it 'should return nil' do
|
194
|
+
subject.users = ['fry', 'bender']
|
195
|
+
subject.rm_user('leela').should be_nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'when user does exist' do
|
200
|
+
it 'should remove the user from the membership array' do
|
201
|
+
subject.users = ['fry', 'bender']
|
202
|
+
subject.rm_user('fry').should eq 'fry'
|
203
|
+
subject.users.should eq ['bender']
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
describe '#has_groupmember?' do
|
210
|
+
subject { Group.new 'baz' }
|
211
|
+
|
212
|
+
context 'when membership array is non-existent' do
|
213
|
+
it 'should be false' do
|
214
|
+
subject.has_groupmember?('foo').should be_false
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'when membership array is populated' do
|
219
|
+
it 'should be true if the user is a member of the group' do
|
220
|
+
subject.groupmembers = ['00000000-0000-0000-0000-000000000001']
|
221
|
+
subject.has_groupmember?('foo').should be_true
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'should be false if the user is NOT a member of the group' do
|
225
|
+
subject.has_groupmember?('leela').should be_false
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
|
231
|
+
describe '#add_groupmember' do
|
232
|
+
subject { Group.new 'baz' }
|
233
|
+
|
234
|
+
context 'when the member does NOT exist' do
|
235
|
+
it 'should raise an error' do
|
236
|
+
expect { subject.add_groupmember('invalid') }.to raise_error(StandardError)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'when the member exists' do
|
241
|
+
it "should add named group's GeneratedUID to the groupmembers array" do
|
242
|
+
subject.add_groupmember('foo').should be_nil
|
243
|
+
subject['groupmembers'].should eql ["00000000-0000-0000-0000-000000000001"]
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
248
|
+
|
249
|
+
describe '#rm_groupmember' do
|
250
|
+
subject { Group.new 'baz' }
|
251
|
+
|
252
|
+
context 'when the member does NOT exist' do
|
253
|
+
it 'should return nil' do
|
254
|
+
subject.rm_groupmember('invalid').should be_nil
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context 'when the member exists' do
|
259
|
+
it 'should remove the member from the array' do
|
260
|
+
['foo', 'bar'].each do |member|
|
261
|
+
subject.add_groupmember member
|
262
|
+
end
|
263
|
+
subject.rm_groupmember('foo').should eq "00000000-0000-0000-0000-000000000001"
|
264
|
+
subject.groupmembers.should eq ["00000000-0000-0000-0000-000000000002"]
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
after :all do
|
272
|
+
FileUtils.rm_rf @test_dir
|
273
|
+
end
|
274
|
+
|
275
|
+
end
|