macadmin 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ describe MacAdmin do
4
+
5
+ describe '#version_string' do
6
+
7
+ it 'should return correct version string' do
8
+ MacAdmin.version_string.should == "macadmin version, #{MacAdmin::VERSION}"
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,145 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ describe MacAdmin::MCX do
5
+
6
+ before :all do
7
+ # Create a dslocal sandbox
8
+ @test_dir = "/private/tmp/macadmin_computer_test.#{rand(100000)}"
9
+ MacAdmin::DSLocalRecord.send(:remove_const, :DSLOCAL_ROOT)
10
+ MacAdmin::DSLocalRecord::DSLOCAL_ROOT = File.expand_path @test_dir
11
+ FileUtils.mkdir_p "#{@test_dir}/Default/computers"
12
+
13
+ # Create two computer record plists that we can load
14
+ planet_express = {
15
+ "en_address" => ["aa:aa:aa:aa:aa:aa"],
16
+ "realname" => ["Planet Express"],
17
+ "name" => ["planet-express"],
18
+ "generateduid" => ["00000000-0000-0000-0000-000000000001"],
19
+ }
20
+
21
+ [planet_express].each do |computer|
22
+ record = CFPropertyList::List.new
23
+ record.value = CFPropertyList.guess(computer)
24
+ record.save("#{@test_dir}/Default/computers/#{computer['name'].first}.plist", CFPropertyList::List::FORMAT_BINARY)
25
+ end
26
+
27
+ @policy_as_file = "#{@test_dir}/policy.plist"
28
+ @raw_xml_content = <<-RAW_XML_CONTENT
29
+ <?xml version="1.0" encoding="UTF-8"?>
30
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
31
+ <plist version="1.0">
32
+ <dict>
33
+ <key>com.apple.SoftwareUpdate</key>
34
+ <dict>
35
+ <key>CatalogURL</key>
36
+ <dict>
37
+ <key>state</key>
38
+ <string>always</string>
39
+ <key>value</key>
40
+ <string>http://foo.bar.com/reposado/html/content/catalogs/index.sucatalog</string>
41
+ </dict>
42
+ </dict>
43
+ <key>com.apple.screensaver</key>
44
+ <dict>
45
+ <key>askForPassword</key>
46
+ <dict>
47
+ <key>state</key>
48
+ <string>once</string>
49
+ <key>value</key>
50
+ <integer>1</integer>
51
+ </dict>
52
+ </dict>
53
+ </dict>
54
+ </plist>
55
+ RAW_XML_CONTENT
56
+
57
+ File.open(@policy_as_file, 'w') { |f| f.write(@raw_xml_content) }
58
+
59
+ end
60
+
61
+ describe "#mcx_import" do
62
+
63
+ context "when the content is XML (String)" do
64
+ subject { Computer.new :name => 'planet-express' }
65
+ it { subject.send(:mcx_import, @raw_xml_content).should be_an_instance_of Array }
66
+ end
67
+
68
+ context "when the content is a file path (String)" do
69
+ subject { Computer.new :name => 'planet-express' }
70
+ it { subject.send(:mcx_import, @policy_as_file).should be_an_instance_of Array }
71
+ end
72
+
73
+ end
74
+
75
+ describe "#mcx_export" do
76
+ subject { Computer.new :name => 'planet-express' }
77
+ it "should return a valid String" do
78
+ subject.mcx_import @raw_xml_content
79
+ subject.mcx_export.should be_an_instance_of String
80
+ end
81
+ end
82
+
83
+ describe '#has_mcx?' do
84
+
85
+ context "when the record has a valid mcx_settings attribute" do
86
+ subject { Computer.new :name => 'planet-express' }
87
+ it do
88
+ subject.send(:mcx_import, @raw_xml_content)
89
+ subject.send(:has_mcx?).should be_true
90
+ end
91
+ end
92
+
93
+ context "when the record has an empty mcx_settings attribute" do
94
+ subject { Computer.new :name => 'planet-express' }
95
+ it do
96
+ subject['mcx_settings'] = []
97
+ subject.send(:has_mcx?).should be_false
98
+ end
99
+ end
100
+
101
+ context "when the record has a bad mcx_settings attribute" do
102
+ subject { Computer.new :name => 'planet-express' }
103
+ it do
104
+ subject['mcx_settings'] = ''
105
+ subject.send(:has_mcx?).should be_false
106
+ end
107
+ end
108
+
109
+ context "when the record has NO mcx_settings attribute" do
110
+ subject { Computer.new :name => 'planet-express' }
111
+ it do
112
+ subject.send(:has_mcx?).should be_false
113
+ end
114
+ end
115
+
116
+ end
117
+
118
+ describe "#mcx_delete" do
119
+
120
+ context "when the object has NO MCX attached" do
121
+ subject { Computer.new :name => 'planet-express' }
122
+ it do
123
+ subject.mcx_delete.should be_nil
124
+ subject['mcx_settings'].should be_nil
125
+ subject['mcx_flags'].should be_nil
126
+ end
127
+ end
128
+
129
+ context "when the object has MCX attached" do
130
+ subject { Computer.new :name => 'planet-express' }
131
+ before { subject.send(:mcx_import, @raw_xml_content) }
132
+ it do
133
+ subject.mcx_delete.should be_true
134
+ subject['mcx_settings'].should be_nil
135
+ subject['mcx_flags'].should be_nil
136
+ end
137
+ end
138
+
139
+ end
140
+
141
+ after :all do
142
+ FileUtils.rm_rf @test_dir
143
+ end
144
+
145
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'macadmin/common'
3
+
4
+ describe MacAdmin::Password do
5
+
6
+ if MacAdmin::Common::MAC_OS_X_PRODUCT_VERSION > 10.7
7
+
8
+ describe '#salted_sha512_pbkdf2' do
9
+ it 'should return an SaltedSHA512PBKDF2 object' do
10
+ MacAdmin::Password.salted_sha512_pbkdf2('').should be_an_instance_of SaltedSHA512PBKDF2
11
+ end
12
+ end
13
+
14
+ describe '#salted_sha512_pbkdf2_from_string' do
15
+ it 'should return an Hash object' do
16
+ MacAdmin::Password.salted_sha512_pbkdf2_from_string('').should be_an_instance_of Hash
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ describe "#salted_sha512" do
23
+ it "should return a SaltedSHA512 object" do
24
+ MacAdmin::Password.salted_sha512('').should be_an_instance_of SaltedSHA512
25
+ end
26
+ end
27
+
28
+ describe "#salted_sha1" do
29
+ it "should return a SaltedSHA1 object" do
30
+ MacAdmin::Password.salted_sha1('').should be_an_instance_of SaltedSHA1
31
+ end
32
+ end
33
+
34
+ describe "#apropos" do
35
+ it "should return a Password object" do
36
+ MacAdmin::Password.salted_sha1('').should be_an_instance_of SaltedSHA1 or SaltedSHA512 or SaltedSHA512PBKDF2
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,309 @@
1
+ require 'spec_helper'
2
+ require 'fileutils'
3
+
4
+ # Override the constant
5
+ MacAdmin::SaltedSHA1.send(:remove_const, :SHADOWHASH_STORE)
6
+ MacAdmin::SaltedSHA1::SHADOWHASH_STORE = '/private/tmp'
7
+
8
+ describe MacAdmin::ShadowHash do
9
+
10
+ before :all do
11
+ @lion_user = <<-USER
12
+ <?xml version="1.0" encoding="UTF-8"?>
13
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
14
+ <plist version="1.0">
15
+ <dict>
16
+ <key>ShadowHashData</key>
17
+ <array>
18
+ <data>
19
+ YnBsaXN0MDDRAQJdU0FMVEVELVNIQTUxMk8QRKYGXxe13dYD0oQmTHNjt7IR
20
+ 34GbwhH34AR2aOTzXsVYo7Xe4aXMOih7nDslKBN4IzbQfxvQV57qzrAPm8mc
21
+ QuNJvB9kCAsZAAAAAAAAAQEAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAGA=
22
+ </data>
23
+ </array>
24
+ </dict>
25
+ </plist>
26
+ USER
27
+
28
+ @mtnlion_user = <<-USER
29
+ <?xml version="1.0" encoding="UTF-8"?>
30
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
31
+ <plist version="1.0">
32
+ <dict>
33
+ <key>ShadowHashData</key>
34
+ <array>
35
+ <data>
36
+ YnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50
37
+ cm9weVRzYWx0Wml0ZXJhdGlvbnNPEIConLEEdxPt9cDsU3xFk6FmdnPj/0F9
38
+ l5KbKL9sn6iz7N/CiltPryTpMJl6I7v0SpO0WkjTzyswmS/ZSfs6lQC8Ecqw
39
+ x5H2N/7C0o6w11PodU1APCIrDoeUJOqO5Zfd3FS/1v6bkhAk3cNYugQvIIsG
40
+ hXHfsg/h5FKpwfK1eUB3Mk8QIGGMkcvRWusdYs5gUqmj0Aw6AO3B8anWCHH+
41
+ b00A2+xrEV0BCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA
42
+ AAAAAOo=
43
+ </data>
44
+ </array>
45
+ </dict>
46
+ </plist>
47
+ USER
48
+ end
49
+
50
+ describe '#create_from_user_record' do
51
+ subject { ShadowHash.create_from_user_record record }
52
+
53
+ context "given a Legacy user record" do
54
+ before do
55
+ @string = 'AA9E81CD14C2BE42D6D85E6ED3B1A8C6176FAEF3EF4E91B7'
56
+ # override the constant
57
+
58
+ # create a password file to read
59
+ @guid = "37C25EE9-C151-4015-9D2B-0402D1CFF50B"
60
+ @shadowhash_file = "#{MacAdmin::SaltedSHA1::SHADOWHASH_STORE}/#{@guid}"
61
+ File.open(@shadowhash_file, mode='w') do |f|
62
+ f.write "0" * 168 + @string + "0" * 1024
63
+ end
64
+ end
65
+
66
+ let(:record) do
67
+ @test_data = { 'generateduid' => @guid }
68
+ end
69
+ it { should be_an_instance_of SaltedSHA1 }
70
+ it { should respond_to :password }
71
+ it { subject.password.should be_an_instance_of String }
72
+
73
+ after do
74
+ FileUtils.rm @shadowhash_file
75
+ end
76
+ end
77
+
78
+ context "given a Lion user record" do
79
+ let(:record) do
80
+ plist = CFPropertyList::List.new(:data => @lion_user)
81
+ @test_data = CFPropertyList.native_types(plist.value)
82
+ end
83
+ it { should be_an_instance_of SaltedSHA512 }
84
+ it { should respond_to :password }
85
+ it { subject.password.should be_an_instance_of Hash }
86
+ end
87
+
88
+ context "given a Mountain Lion user record" do
89
+ let(:record) do
90
+ plist = CFPropertyList::List.new(:data => @mtnlion_user)
91
+ @test_data = CFPropertyList.native_types(plist.value)
92
+ end
93
+ it { should be_an_instance_of SaltedSHA512PBKDF2 }
94
+ it { should respond_to :password }
95
+ it { subject.password.should be_an_instance_of Hash }
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+
102
+ describe MacAdmin::SaltedSHA1 do
103
+
104
+ before :all do
105
+ @string = 'AA9E81CD14C2BE42D6D85E6ED3B1A8C6176FAEF3EF4E91B7'
106
+ @password = SaltedSHA1.new @string
107
+ end
108
+
109
+ describe '#new' do
110
+ it 'returns a new SaltedSHA1 object' do
111
+ @password.should be_an_instance_of SaltedSHA1
112
+ end
113
+
114
+ it 'throws an ArgumentError when given fewer than 1 params' do
115
+ lambda { SaltedSHA1.new }.should raise_exception ArgumentError
116
+ end
117
+
118
+ it 'only accepts a valid 24 byte hexadecimal string' do
119
+ short_string = 'AA9E81CD14C2BE42D6D85E6ED3B1A8C6176FAEF3EF4E91B' # 23 bytes
120
+ lambda { SaltedSHA1.new short_string }.should raise_exception ArgumentError
121
+ end
122
+
123
+ end
124
+
125
+ describe '#validate' do
126
+ it 'returns the original hexadecimal string when successfully parsed' do
127
+ @password.validate(@string).should eq @string
128
+ end
129
+ end
130
+
131
+ describe '#data' do
132
+ subject { @password.data }
133
+ it { should be_an_instance_of String }
134
+ it { subject.length.should eq 48 }
135
+ end
136
+
137
+ describe '#password' do
138
+ subject { @password.password }
139
+ it { should be_an_instance_of String }
140
+ it { subject.length.should eq 48 }
141
+ end
142
+
143
+ describe '#write_to_shadowhash_file' do
144
+ before do
145
+ @guid = '6D11E403-1F9E-481B-874A-75FA45AB4AF9'
146
+ @user = User.new :name => 'foo', :generateduid => @guid
147
+ end
148
+ it 'creates a new ShadowHash file' do
149
+ @password.send(:write_to_shadowhash_file, @user).should be_true
150
+ end
151
+ after do
152
+ FileUtils.rm "/private/tmp/#{@guid}"
153
+ end
154
+
155
+ end
156
+
157
+ describe '#remove_shadowhash_file' do
158
+ before do
159
+ @guid = '72196697-FE91-40B2-9B11-B3ACF1031E79'
160
+ @user = User.new :name => 'foo', :generateduid => @guid
161
+ @password.send(:write_to_shadowhash_file, @user)
162
+ end
163
+ it 'removes the associated ShadowHash file' do
164
+ @password.send(:remove_shadowhash_file, @user).should be_true
165
+ end
166
+ after do
167
+ FileUtils.rm "/private/tmp/#{@guid}" if File.exists? "/private/tmp/#{@guid}"
168
+ end
169
+ end
170
+
171
+ describe '#create_from_shadowhash_file' do
172
+ before do
173
+ @guid = "37C25EE9-C151-4015-9D2B-0402D1CFF50B"
174
+ @shadowhash_file = "#{MacAdmin::SaltedSHA1::SHADOWHASH_STORE}/#{@guid}"
175
+ File.open(@shadowhash_file, mode='w') do |f|
176
+ f.write "0" * 168 + @string + "0" * 1024
177
+ end
178
+ end
179
+ subject { SaltedSHA1.create_from_shadowhash_file @guid }
180
+ # subject { SaltedSHA512PBKDF2.create_from_shadowhashdata(@test_data) }
181
+ it { should be_an_instance_of SaltedSHA1 }
182
+ it { should respond_to :password }
183
+ it { subject.password.should be_an_instance_of String }
184
+ after do
185
+ FileUtils.rm @shadowhash_file
186
+ end
187
+ end
188
+
189
+ end
190
+
191
+ describe MacAdmin::SaltedSHA512 do
192
+
193
+ before :all do
194
+ @string = 'a6055f17b5ddd603d284264c7363b7b211df819bc211f7e0047668e4f35ec558a3b5dee1a5cc3a287b9c3b252813782336d07f1bd0579eeaceb00f9bc99c42e349bc1f64'
195
+ @password = SaltedSHA512.new @string
196
+ end
197
+
198
+ describe '#new' do
199
+ it 'returns a new SaltedSHA512 object' do
200
+ @password.should be_an_instance_of SaltedSHA512
201
+ end
202
+
203
+ it 'throws an ArgumentError when given fewer than 1 params' do
204
+ lambda { SaltedSHA512.new }.should raise_exception ArgumentError
205
+ end
206
+
207
+ it 'only accepts a valid 68 byte hexadecimal string' do
208
+ short_string = 'a6055f17b5ddd603d284264c7363b7b211df819bc211f7e0047668e4f35ec558a3b5dee1a5cc3a287b9c3b252813782336d07f1bd0579eeaceb00f9bc99c42e349bc1f' # 67 bytes
209
+ lambda { SaltedSHA512.new }.should raise_exception ArgumentError
210
+ end
211
+
212
+ end
213
+
214
+ describe '#validate' do
215
+ it 'returns the original hexadecimal string when successfully parsed' do
216
+ @password.validate(@string).should eq @string
217
+ end
218
+ end
219
+
220
+ describe '#data' do
221
+ it 'returns a string-encoded Binary Property List' do
222
+ @password.data.should match /^bplist/
223
+ end
224
+ end
225
+
226
+ describe '#password' do
227
+ it 'returns a Hash representation of the password' do
228
+ @password.password.should be_an_instance_of Hash
229
+ end
230
+ end
231
+
232
+ describe '#create_from_shadowhashdata' do
233
+ before do
234
+ plist = CFPropertyList::List.new(:data => @password.data)
235
+ @test_data = CFPropertyList.native_types(plist.value)
236
+ end
237
+ subject { SaltedSHA512.create_from_shadowhashdata(@test_data) }
238
+ it { should be_an_instance_of SaltedSHA512 }
239
+ it { should respond_to :password }
240
+ it { subject.password.should be_an_instance_of Hash }
241
+ end
242
+
243
+ end
244
+
245
+ describe MacAdmin::SaltedSHA512PBKDF2 do
246
+
247
+ before :all do
248
+ @entropy = '969b62dca51304cc0852dc547309643b480d656f83df441aeaed342c836ddc730bc6350a1a61dd847a1aee910c8648a4a895b07addd066a45cf1233c52c3c73adb5f77d0ba82b71d134a8e9a0c0ed5d6a0e789bfae6beb48bf48e34bfd20509e7b22763753fbd4ae302e27717cea3deede0b38fc52640e5229a7dcbc8d66d609'
249
+ @salt = '79e5cb312d40cc7e89b00f67f928a31c221213bd73cba0f58dc3bcd4215f6552'
250
+ @iterations = 29069
251
+
252
+ @hash_params = { :iterations => @iterations, :entropy => @entropy, :salt => @salt }
253
+ @array_params = [ @entropy, @salt, @iterations ]
254
+
255
+ @password_with_hash = SaltedSHA512PBKDF2.new @hash_params
256
+ @password_with_array = SaltedSHA512PBKDF2.new @array_params
257
+ end
258
+
259
+ describe '#new' do
260
+ it 'returns a new SaltedSHA512PBKDF2 object when given a Hash' do
261
+ @password_with_hash.should be_an_instance_of SaltedSHA512PBKDF2
262
+ end
263
+
264
+ it 'returns a new SaltedSHA512PBKDF2 object when given a Array' do
265
+ @password_with_array.should be_an_instance_of SaltedSHA512PBKDF2
266
+ end
267
+
268
+ it "throws an ArgumentError when given fewer than 3 params" do
269
+ lambda { SaltedSHA512PBKDF2.new @entropy, @iterations }.should raise_exception ArgumentError
270
+ end
271
+
272
+ it 'only accepts a valid 68 byte hexadecimal string' do
273
+ short_string = 'a6055f17b5ddd603d284264c7363b7b211df819bc211f7e0047668e4f35ec558a3b5dee1a5cc3a287b9c3b252813782336d07f1bd0579eeaceb00f9bc99c42e349bc1f' # 67 bytes
274
+ lambda { SaltedSHA512PBKDF2.new }.should raise_exception ArgumentError
275
+ end
276
+
277
+ end
278
+
279
+ describe '#validate' do
280
+ it 'returns a Hash representation of the original parameters' do
281
+ @password_with_array.validate(@array_params).should eq @hash_params
282
+ end
283
+ end
284
+
285
+ describe '#data' do
286
+ it 'returns a string-encoded Binary Property List' do
287
+ @password_with_array.data.should match /^bplist/
288
+ end
289
+ end
290
+
291
+ describe '#password' do
292
+ it 'returns a Hash representation of the password' do
293
+ @password_with_array.password.should be_an_instance_of Hash
294
+ end
295
+ end
296
+
297
+ describe '#create_from_shadowhashdata' do
298
+ before do
299
+ plist = CFPropertyList::List.new(:data => @password_with_array.data)
300
+ @test_data = CFPropertyList.native_types(plist.value)
301
+ end
302
+ subject { SaltedSHA512PBKDF2.create_from_shadowhashdata(@test_data) }
303
+ it { should be_an_instance_of SaltedSHA512PBKDF2 }
304
+ it { should respond_to :password }
305
+ it { subject.password.should be_an_instance_of Hash }
306
+ end
307
+
308
+ end
309
+