macadmin 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+