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.
- 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
data/spec/mcx_spec.rb
ADDED
@@ -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
|
+
|