puppet 3.0.1 → 3.0.2.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puppet might be problematic. Click here for more details.
- data/Gemfile +17 -3
- data/Rakefile +7 -5
- data/ext/build_defaults.yaml +1 -1
- data/ext/debian/puppet.init +33 -34
- data/ext/debian/puppet.logrotate +2 -1
- data/ext/debian/puppetmaster.init +1 -2
- data/ext/suse/client.init +1 -1
- data/ext/suse/puppet.spec +3 -0
- data/ext/suse/server.init +1 -1
- data/ext/windows/service/daemon.rb +1 -1
- data/install.rb +32 -53
- data/lib/hiera/backend/puppet_backend.rb +6 -5
- data/lib/puppet/agent.rb +3 -3
- data/lib/puppet/application/agent.rb +1 -2
- data/lib/puppet/application/cert.rb +4 -5
- data/lib/puppet/application/kick.rb +3 -0
- data/lib/puppet/defaults.rb +15 -2
- data/lib/puppet/indirector/exec.rb +1 -1
- data/lib/puppet/module_tool/skeleton/templates/generator/manifests/init.pp.erb +1 -1
- data/lib/puppet/parser/functions/hiera_include.rb +6 -4
- data/lib/puppet/parser/lexer.rb +38 -1
- data/lib/puppet/parser/relationship.rb +3 -1
- data/lib/puppet/provider.rb +1 -1
- data/lib/puppet/provider/augeas/augeas.rb +1 -1
- data/lib/puppet/provider/file/windows.rb +10 -29
- data/lib/puppet/provider/group/ldap.rb +1 -1
- data/lib/puppet/provider/group/windows_adsi.rb +1 -1
- data/lib/puppet/provider/ldap.rb +5 -1
- data/lib/puppet/provider/macauthorization/macauthorization.rb +2 -6
- data/lib/puppet/provider/package/dpkg.rb +8 -12
- data/lib/puppet/provider/package/macports.rb +2 -2
- data/lib/puppet/provider/package/msi.rb +2 -2
- data/lib/puppet/provider/package/sun.rb +1 -1
- data/lib/puppet/provider/package/windows.rb +2 -2
- data/lib/puppet/provider/package/yum.rb +5 -1
- data/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb +2 -2
- data/lib/puppet/provider/service/freebsd.rb +1 -1
- data/lib/puppet/provider/user/directoryservice.rb +603 -65
- data/lib/puppet/provider/user/windows_adsi.rb +1 -1
- data/lib/puppet/provider/zpool/zpool.rb +1 -1
- data/lib/puppet/run.rb +2 -1
- data/lib/puppet/settings.rb +9 -5
- data/lib/puppet/ssl/certificate_authority.rb +2 -0
- data/lib/puppet/transaction.rb +1 -1
- data/lib/puppet/type/cron.rb +4 -4
- data/lib/puppet/type/exec.rb +10 -5
- data/lib/puppet/type/file.rb +1 -1
- data/lib/puppet/type/service.rb +3 -2
- data/lib/puppet/type/user.rb +24 -2
- data/lib/puppet/util.rb +3 -6
- data/lib/puppet/util/adsi.rb +3 -9
- data/lib/puppet/util/diff.rb +1 -1
- data/lib/puppet/util/execution.rb +13 -6
- data/lib/puppet/util/feature.rb +3 -1
- data/lib/puppet/util/log/destinations.rb +12 -16
- data/lib/puppet/util/selinux.rb +18 -2
- data/lib/puppet/util/windows.rb +1 -0
- data/lib/puppet/util/windows/security.rb +2 -39
- data/lib/puppet/util/windows/sid.rb +96 -0
- data/lib/puppet/version.rb +1 -1
- data/spec/integration/util/windows/security_spec.rb +3 -23
- data/spec/unit/agent_spec.rb +7 -3
- data/spec/unit/application/agent_spec.rb +13 -5
- data/spec/unit/daemon_spec.rb +2 -1
- data/spec/unit/hiera/backend/puppet_backend_spec.rb +49 -42
- data/spec/unit/indirector/exec_spec.rb +8 -6
- data/spec/unit/parser/functions/hiera_include_spec.rb +11 -4
- data/spec/unit/parser/lexer_spec.rb +120 -8
- data/spec/unit/parser/relationship_spec.rb +24 -0
- data/spec/unit/provider/file/windows_spec.rb +29 -29
- data/spec/unit/provider/group/windows_adsi_spec.rb +2 -2
- data/spec/unit/provider/nameservice/directoryservice_spec.rb +1 -1
- data/spec/unit/provider/package/dpkg_spec.rb +2 -2
- data/spec/unit/provider/package/macports_spec.rb +6 -5
- data/spec/unit/provider/package/msi_spec.rb +1 -1
- data/spec/unit/provider/package/pacman_spec.rb +1 -1
- data/spec/unit/provider/package/rpm_spec.rb +1 -1
- data/spec/unit/provider/package/sun_spec.rb +4 -4
- data/spec/unit/provider/package/windows_spec.rb +1 -1
- data/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb +14 -11
- data/spec/unit/provider/user/directoryservice_spec.rb +943 -0
- data/spec/unit/provider/user/ldap_spec.rb +22 -8
- data/spec/unit/provider/user/windows_adsi_spec.rb +4 -4
- data/spec/unit/provider_spec.rb +1 -1
- data/spec/unit/run_spec.rb +1 -1
- data/spec/unit/settings_spec.rb +16 -0
- data/spec/unit/ssl/certificate_authority_spec.rb +24 -0
- data/spec/unit/util/adsi_spec.rb +4 -8
- data/spec/unit/util/diff_spec.rb +2 -2
- data/spec/unit/util/execution_spec.rb +78 -20
- data/spec/unit/util/feature_spec.rb +12 -1
- data/spec/unit/util/selinux_spec.rb +20 -0
- data/spec/unit/util/windows/sid_spec.rb +100 -0
- data/spec/unit/util_spec.rb +17 -0
- metadata +71 -48
- data/Gemfile.lock +0 -44
- data/lib/puppet/provider/interface/base.rb +0 -0
@@ -6,7 +6,7 @@ describe Puppet::Type.type(:package).provider(:windows) do
|
|
6
6
|
let (:source) { 'E:\mysql-5.1.58-win-x64.msi' }
|
7
7
|
let (:resource) { Puppet::Type.type(:package).new(:name => name, :provider => :windows, :source => source) }
|
8
8
|
let (:provider) { resource.provider }
|
9
|
-
let (:execute_options) do {:combine => true} end
|
9
|
+
let (:execute_options) do {:failonfail => false, :combine => true} end
|
10
10
|
|
11
11
|
before :each do
|
12
12
|
# make sure we never try to execute anything
|
@@ -565,29 +565,32 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if
|
|
565
565
|
end
|
566
566
|
end
|
567
567
|
|
568
|
-
describe '#user_insync?' do
|
568
|
+
describe '#user_insync?', :if => Puppet.features.microsoft_windows? do
|
569
569
|
let(:resource) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') }
|
570
570
|
|
571
|
-
before :each do
|
572
|
-
Puppet::Util::ADSI.stubs(:sid_for_account).with('system').returns('SYSTEM SID')
|
573
|
-
Puppet::Util::ADSI.stubs(:sid_for_account).with('joe').returns('SID A')
|
574
|
-
Puppet::Util::ADSI.stubs(:sid_for_account).with('MACHINE\joe').returns('SID A')
|
575
|
-
Puppet::Util::ADSI.stubs(:sid_for_account).with('bob').returns('SID B')
|
576
|
-
end
|
577
|
-
|
578
571
|
it 'should consider the user as in sync if the name matches' do
|
572
|
+
Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').twice.returns('SID A')
|
573
|
+
|
579
574
|
resource.should be_user_insync('joe', ['joe'])
|
580
575
|
end
|
581
576
|
|
582
577
|
it 'should consider the user as in sync if the current user is fully qualified' do
|
578
|
+
Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').returns('SID A')
|
579
|
+
Puppet::Util::Windows::Security.expects(:name_to_sid).with('MACHINE\joe').returns('SID A')
|
580
|
+
|
583
581
|
resource.should be_user_insync('MACHINE\joe', ['joe'])
|
584
582
|
end
|
585
583
|
|
586
584
|
it 'should consider a current user of the empty string to be the same as the system user' do
|
585
|
+
Puppet::Util::Windows::Security.expects(:name_to_sid).with('system').twice.returns('SYSTEM SID')
|
586
|
+
|
587
587
|
resource.should be_user_insync('', ['system'])
|
588
588
|
end
|
589
589
|
|
590
590
|
it 'should consider different users as being different' do
|
591
|
+
Puppet::Util::Windows::Security.expects(:name_to_sid).with('joe').returns('SID A')
|
592
|
+
Puppet::Util::Windows::Security.expects(:name_to_sid).with('bob').returns('SID B')
|
593
|
+
|
591
594
|
resource.should_not be_user_insync('joe', ['bob'])
|
592
595
|
end
|
593
596
|
end
|
@@ -1456,7 +1459,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if
|
|
1456
1459
|
end
|
1457
1460
|
end
|
1458
1461
|
|
1459
|
-
describe '#user=' do
|
1462
|
+
describe '#user=', :if => Puppet.features.microsoft_windows? do
|
1460
1463
|
before :each do
|
1461
1464
|
@mock_task = mock
|
1462
1465
|
@mock_task.responds_like(Win32::TaskScheduler.new)
|
@@ -1466,7 +1469,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if
|
|
1466
1469
|
end
|
1467
1470
|
|
1468
1471
|
it 'should use nil for user and password when setting the user to the SYSTEM account' do
|
1469
|
-
Puppet::Util::
|
1472
|
+
Puppet::Util::Windows::Security.stubs(:name_to_sid).with('system').returns('SYSTEM SID')
|
1470
1473
|
|
1471
1474
|
resource = Puppet::Type.type(:scheduled_task).new(
|
1472
1475
|
:name => 'Test Task',
|
@@ -1480,7 +1483,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if
|
|
1480
1483
|
end
|
1481
1484
|
|
1482
1485
|
it 'should use the specified user and password when setting the user to anything other than SYSTEM' do
|
1483
|
-
Puppet::Util::
|
1486
|
+
Puppet::Util::Windows::Security.stubs(:name_to_sid).with('my_user_name').returns('SID A')
|
1484
1487
|
|
1485
1488
|
resource = Puppet::Type.type(:scheduled_task).new(
|
1486
1489
|
:name => 'Test Task',
|
@@ -0,0 +1,943 @@
|
|
1
|
+
#! /usr/bin/env ruby -S rspec
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'facter/util/plist'
|
4
|
+
|
5
|
+
describe Puppet::Type.type(:user).provider(:directoryservice) do
|
6
|
+
let(:username) { 'nonexistant_user' }
|
7
|
+
let(:user_path) { "/Users/#{username}" }
|
8
|
+
let(:resource) do
|
9
|
+
Puppet::Type.type(:user).new(
|
10
|
+
:name => username,
|
11
|
+
:provider => :directoryservice
|
12
|
+
)
|
13
|
+
end
|
14
|
+
let(:provider) { resource.provider }
|
15
|
+
let(:users_plist_dir) { '/var/db/dslocal/nodes/Default/users' }
|
16
|
+
|
17
|
+
# This is the output of doing `dscl -plist . read /Users/<username>` which
|
18
|
+
# will return a hash of keys whose values are all arrays.
|
19
|
+
let(:user_plist_xml) do
|
20
|
+
'<?xml version="1.0" encoding="UTF-8"?>
|
21
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
22
|
+
<plist version="1.0">
|
23
|
+
<dict>
|
24
|
+
<key>dsAttrTypeStandard:NFSHomeDirectory</key>
|
25
|
+
<array>
|
26
|
+
<string>/Users/nonexistant_user</string>
|
27
|
+
</array>
|
28
|
+
<key>dsAttrTypeStandard:RealName</key>
|
29
|
+
<array>
|
30
|
+
<string>nonexistant_user</string>
|
31
|
+
</array>
|
32
|
+
<key>dsAttrTypeStandard:PrimaryGroupID</key>
|
33
|
+
<array>
|
34
|
+
<string>22</string>
|
35
|
+
</array>
|
36
|
+
<key>dsAttrTypeStandard:UniqueID</key>
|
37
|
+
<array>
|
38
|
+
<string>1000</string>
|
39
|
+
</array>
|
40
|
+
<key>dsAttrTypeStandard:RecordName</key>
|
41
|
+
<array>
|
42
|
+
<string>nonexistant_user</string>
|
43
|
+
</array>
|
44
|
+
</dict>
|
45
|
+
</plist>'
|
46
|
+
end
|
47
|
+
|
48
|
+
# This is the same as above, however in a native Ruby hash instead
|
49
|
+
# of XML
|
50
|
+
let(:user_plist_hash) do
|
51
|
+
{
|
52
|
+
"dsAttrTypeStandard:RealName" => [username],
|
53
|
+
"dsAttrTypeStandard:NFSHomeDirectory" => [user_path],
|
54
|
+
"dsAttrTypeStandard:PrimaryGroupID" => ["22"],
|
55
|
+
"dsAttrTypeStandard:UniqueID" => ["1000"],
|
56
|
+
"dsAttrTypeStandard:RecordName" => [username]
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
# The below value is the result of executing
|
61
|
+
# `dscl -plist . read /Users/<username> ShadowHashData` on a 10.7
|
62
|
+
# system and converting it to a native Ruby Hash with Plist.parse_xml
|
63
|
+
let(:sha512_shadowhashdata_hash) do
|
64
|
+
{
|
65
|
+
'dsAttrTypeNative:ShadowHashData' => ['62706c69 73743030 d101025d 53414c54 45442d53 48413531 324f1044 7ea7d592 131f57b2 c8f8bdbc ec8d9df1 2128a386 393a4f00 c7619bac 2622a44d 451419d1 1da512d5 915ab98e 39718ac9 4083fe2e fd6bf710 a54d477f 8ff735b1 2587192d 080b1900 00000000 00010100 00000000 00000300 00000000 00000000 00000000 000060']
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# The below is a binary plist that is stored in the ShadowHashData key
|
70
|
+
# on a 10.7 system.
|
71
|
+
let(:sha512_embedded_bplist) do
|
72
|
+
"bplist00\321\001\002]SALTED-SHA512O\020D~\247\325\222\023\037W\262\310\370\275\274\354\215\235\361!(\243\2069:O\000\307a\233\254&\"\244ME\024\031\321\035\245\022\325\221Z\271\2169q\212\311@\203\376.\375k\367\020\245MG\177\217\3675\261%\207\031-\b\v\031\000\000\000\000\000\000\001\001\000\000\000\000\000\000\000\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000`"
|
73
|
+
end
|
74
|
+
|
75
|
+
# The below is a Base64 encoded string representing a salted-SHA512 password
|
76
|
+
# hash.
|
77
|
+
let(:sha512_pw_string) do
|
78
|
+
"~\247\325\222\023\037W\262\310\370\275\274\354\215\235\361!(\243\2069:O\000\307a\233\254&\"\244ME\024\031\321\035\245\022\325\221Z\271\2169q\212\311@\203\376.\375k\367\020\245MG\177\217\3675\261%\207\031-"
|
79
|
+
end
|
80
|
+
|
81
|
+
# The below is the result of converting sha512_embedded_bplist to XML and
|
82
|
+
# parsing it with Plist.parse_xml. It is a Ruby Hash whose value is a
|
83
|
+
# StringIO object holding a Base64 encoded salted-SHA512 password hash.
|
84
|
+
let(:sha512_embedded_bplist_hash) do
|
85
|
+
{ 'SALTED-SHA512' => StringIO.new(sha512_pw_string) }
|
86
|
+
end
|
87
|
+
|
88
|
+
# The value below is the result of converting sha512_pw_string to Hex.
|
89
|
+
let(:sha512_password_hash) do
|
90
|
+
'7ea7d592131f57b2c8f8bdbcec8d9df12128a386393a4f00c7619bac2622a44d451419d11da512d5915ab98e39718ac94083fe2efd6bf710a54d477f8ff735b12587192d'
|
91
|
+
end
|
92
|
+
|
93
|
+
# The below value is the result of executing
|
94
|
+
# `dscl -plist . read /Users/<username> ShadowHashData` on a 10.8
|
95
|
+
# system and converting it to a native Ruby Hash with Plist.parse_xml
|
96
|
+
let(:pbkdf2_shadowhashdata_hash) do
|
97
|
+
{
|
98
|
+
"dsAttrTypeNative:ShadowHashData"=>["62706c69 73743030 d101025f 10145341 4c544544 2d534841 3531322d 50424b44 4632d303 04050607 0857656e 74726f70 79547361 6c745a69 74657261 74696f6e 734f1080 0590ade1 9e6953c1 35ae872a e7761823 5df7d46c 63de7f9a 0fcdf2cd 9e7d85e4 b7ca8681 01235b61 58e05a30 9805ee48 14b027a4 be9c23ec 2926bc81 72269aff ba5c9a59 85e81091 fa689807 6d297f1f aa75fa61 7551ef16 71d75200 55c4a0d9 7b9b9c58 05aa322b aedbcd8e e9c52381 1653ac2e a9e9c8d8 f1ac519a 0f2b595e 4f102093 77c46908 a1c8ac2c 3e45c0d4 4da8ad0f cd85ec5c 14d9a59f fc40c9da 31f0ec11 60b0080b 22293136 41c4e700 00000000 00010100 00000000 00000900 00000000 00000000 00000000 0000ea"]
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
# The below value is the result of converting pbkdf2_embedded_bplist to XML and
|
103
|
+
# parsing it with Plist.parse_xml.
|
104
|
+
let(:pbkdf2_embedded_bplist_hash) do
|
105
|
+
{
|
106
|
+
'SALTED-SHA512-PBKDF2' => {
|
107
|
+
'entropy' => StringIO.new(pbkdf2_pw_string),
|
108
|
+
'salt' => StringIO.new(pbkdf2_salt_string),
|
109
|
+
'iterations' => pbkdf2_iterations_value
|
110
|
+
}
|
111
|
+
}
|
112
|
+
end
|
113
|
+
|
114
|
+
# The value below is the result of converting pbkdf2_pw_string to Hex.
|
115
|
+
let(:pbkdf2_password_hash) do
|
116
|
+
'0590ade19e6953c135ae872ae77618235df7d46c63de7f9a0fcdf2cd9e7d85e4b7ca868101235b6158e05a309805ee4814b027a4be9c23ec2926bc8172269affba5c9a5985e81091fa6898076d297f1faa75fa617551ef1671d7520055c4a0d97b9b9c5805aa322baedbcd8ee9c523811653ac2ea9e9c8d8f1ac519a0f2b595e'
|
117
|
+
end
|
118
|
+
|
119
|
+
# The below is a binary plist that is stored in the ShadowHashData key
|
120
|
+
# of a 10.8 system.
|
121
|
+
let(:pbkdf2_embedded_plist) do
|
122
|
+
"bplist00\321\001\002_\020\024SALTED-SHA512-PBKDF2\323\003\004\005\006\a\bWentropyTsaltZiterationsO\020\200\005\220\255\341\236iS\3015\256\207*\347v\030#]\367\324lc\336\177\232\017\315\362\315\236}\205\344\267\312\206\201\001#[aX\340Z0\230\005\356H\024\260'\244\276\234#\354)&\274\201r&\232\377\272\\\232Y\205\350\020\221\372h\230\am)\177\037\252u\372auQ\357\026q\327R\000U\304\240\331{\233\234X\005\2522+\256\333\315\216\351\305#\201\026S\254.\251\351\310\330\361\254Q\232\017+Y^O\020 \223w\304i\b\241\310\254,>E\300\324M\250\255\017\315\205\354\\\024\331\245\237\374@\311\3321\360\354\021`\260\b\v\")16A\304\347\000\000\000\000\000\000\001\001\000\000\000\000\000\000\000\t\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\352"
|
123
|
+
end
|
124
|
+
|
125
|
+
# The below value is a Base64 encoded string representing a PBKDF2 password
|
126
|
+
# hash.
|
127
|
+
let(:pbkdf2_pw_string) do
|
128
|
+
"\005\220\255\341\236iS\3015\256\207*\347v\030#]\367\324lc\336\177\232\017\315\362\315\236}\205\344\267\312\206\201\001#[aX\340Z0\230\005\356H\024\260'\244\276\234#\354)&\274\201r&\232\377\272\\\232Y\205\350\020\221\372h\230\am)\177\037\252u\372auQ\357\026q\327R\000U\304\240\331{\233\234X\005\2522+\256\333\315\216\351\305#\201\026S\254.\251\351\310\330\361\254Q\232\017+Y^"
|
129
|
+
end
|
130
|
+
|
131
|
+
# The below value is a Base64 encoded string representing a PBKDF2 salt
|
132
|
+
# string.
|
133
|
+
let(:pbkdf2_salt_string) do
|
134
|
+
"\223w\304i\b\241\310\254,>E\300\324M\250\255\017\315\205\354\\\024\331\245\237\374@\311\3321\360\354"
|
135
|
+
end
|
136
|
+
|
137
|
+
# The below value represents the Hex value of a PBKDF2 salt string
|
138
|
+
let(:pbkdf2_salt_value) do
|
139
|
+
"9377c46908a1c8ac2c3e45c0d44da8ad0fcd85ec5c14d9a59ffc40c9da31f0ec"
|
140
|
+
end
|
141
|
+
|
142
|
+
# The below value is a Fixnum iterations value used in the PBKDF2
|
143
|
+
# key stretching algorithm
|
144
|
+
let(:pbkdf2_iterations_value) do
|
145
|
+
24752
|
146
|
+
end
|
147
|
+
|
148
|
+
# The below represents output of 'dscl -plist . readall /Users' if
|
149
|
+
# only one user were installed on the system. This lets us check
|
150
|
+
# the behavior of all the methods necessary to return a user's
|
151
|
+
# groups property by controlling the data provided by dscl
|
152
|
+
let(:testuser_plist) do
|
153
|
+
'<?xml version="1.0" encoding="UTF-8"?>
|
154
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
155
|
+
<plist version="1.0">
|
156
|
+
<array>
|
157
|
+
<dict>
|
158
|
+
<key>dsAttrTypeNative:KerberosKeys</key>
|
159
|
+
<array>
|
160
|
+
<string>30820157 a1030201 02a08201 4e308201 4a3074a1 2b3029a0 03020112 a1220420 54af3992 1c198bf8 94585a6b 2fba445b c8482228 0dcad666 ea62e038 99e59c45 a2453043 a0030201 03a13c04 3a4c4b44 433a5348 41312e34 33383345 31353244 39443339 34414133 32443133 41453938 46364636 45314645 38443030 46383174 65737475 73657230 64a11b30 19a00302 0111a112 04106375 7d97b2ce ca8343a6 3b0f73d5 1001a245 3043a003 020103a1 3c043a4c 4b44433a 53484131 2e343338 33453135 32443944 33393441 41333244 31334145 39384636 46364531 46453844 30304638 31746573 74757365 72306ca1 233021a0 03020110 a11a0418 67b09be3 5131b670 f8e9265e 62459b4c 19435419 fe918519 a2453043 a0030201 03a13c04 3a4c4b44 433a5348 41312e34 33383345 31353244 39443339 34414133 32443133 41453938 46364636 45314645 38443030 46383174 65737475 736572</string>
|
161
|
+
</array>
|
162
|
+
<key>dsAttrTypeNative:ShadowHashData</key>
|
163
|
+
<array>
|
164
|
+
<string>62706c69 73743030 d101025d 53414c54 45442d53 48413531 324f1044 7ea7d592 131f57b2 c8f8bdbc ec8d9df1 2128a386 393a4f00 c7619bac 2622a44d 451419d1 1da512d5 915ab98e 39718ac9 4083fe2e fd6bf710 a54d477f 8ff735b1 2587192d 080b1900 00000000 00010100 00000000 00000300 00000000 00000000 00000000 000060</string>
|
165
|
+
</array>
|
166
|
+
<key>dsAttrTypeStandard:AppleMetaNodeLocation</key>
|
167
|
+
<array>
|
168
|
+
<string>/Local/Default</string>
|
169
|
+
</array>
|
170
|
+
<key>dsAttrTypeStandard:AuthenticationAuthority</key>
|
171
|
+
<array>
|
172
|
+
<string>;Kerberosv5;;testuser@LKDC:SHA1.4383E152D9D394AA32D13AE98F6F6E1FE8D00F81;LKDC:SHA1.4383E152D9D394AA32D13AE98F6F6E1FE8D00F81</string>
|
173
|
+
<string>;ShadowHash;HASHLIST:<SALTED-SHA512></string>
|
174
|
+
</array>
|
175
|
+
<key>dsAttrTypeStandard:AuthenticationHint</key>
|
176
|
+
<array>
|
177
|
+
<string></string>
|
178
|
+
</array>
|
179
|
+
<key>dsAttrTypeStandard:GeneratedUID</key>
|
180
|
+
<array>
|
181
|
+
<string>0A7D5B63-3AD4-4CA7-B03E-85876F1D1FB3</string>
|
182
|
+
</array>
|
183
|
+
<key>dsAttrTypeStandard:NFSHomeDirectory</key>
|
184
|
+
<array>
|
185
|
+
<string>/Users/nonexistant_user</string>
|
186
|
+
</array>
|
187
|
+
<key>dsAttrTypeStandard:Password</key>
|
188
|
+
<array>
|
189
|
+
<string>********</string>
|
190
|
+
</array>
|
191
|
+
<key>dsAttrTypeStandard:PasswordPolicyOptions</key>
|
192
|
+
<array>
|
193
|
+
<string><?xml version="1.0" encoding="UTF-8"?>
|
194
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
195
|
+
<plist version="1.0">
|
196
|
+
<dict>
|
197
|
+
<key>failedLoginCount</key>
|
198
|
+
<integer>0</integer>
|
199
|
+
<key>failedLoginTimestamp</key>
|
200
|
+
<date>2001-01-01T00:00:00Z</date>
|
201
|
+
<key>lastLoginTimestamp</key>
|
202
|
+
<date>2001-01-01T00:00:00Z</date>
|
203
|
+
<key>passwordTimestamp</key>
|
204
|
+
<date>2012-08-10T23:53:50Z</date>
|
205
|
+
</dict>
|
206
|
+
</plist>
|
207
|
+
</string>
|
208
|
+
</array>
|
209
|
+
<key>dsAttrTypeStandard:PrimaryGroupID</key>
|
210
|
+
<array>
|
211
|
+
<string>22</string>
|
212
|
+
</array>
|
213
|
+
<key>dsAttrTypeStandard:RealName</key>
|
214
|
+
<array>
|
215
|
+
<string>nonexistant_user</string>
|
216
|
+
</array>
|
217
|
+
<key>dsAttrTypeStandard:RecordName</key>
|
218
|
+
<array>
|
219
|
+
<string>nonexistant_user</string>
|
220
|
+
</array>
|
221
|
+
<key>dsAttrTypeStandard:RecordType</key>
|
222
|
+
<array>
|
223
|
+
<string>dsRecTypeStandard:Users</string>
|
224
|
+
</array>
|
225
|
+
<key>dsAttrTypeStandard:UniqueID</key>
|
226
|
+
<array>
|
227
|
+
<string>1000</string>
|
228
|
+
</array>
|
229
|
+
<key>dsAttrTypeStandard:UserShell</key>
|
230
|
+
<array>
|
231
|
+
<string>/bin/bash</string>
|
232
|
+
</array>
|
233
|
+
</dict>
|
234
|
+
</array>
|
235
|
+
</plist>'
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
|
240
|
+
# The below represents the result of running Plist.parse_xml on XML
|
241
|
+
# data returned from the `dscl -plist . readall /Groups` command.
|
242
|
+
# (AKA: What the get_list_of_groups method returns)
|
243
|
+
let(:group_plist_hash_guid) do
|
244
|
+
[{
|
245
|
+
'dsAttrTypeStandard:RecordName' => ['testgroup'],
|
246
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
247
|
+
username,
|
248
|
+
'jeff',
|
249
|
+
'zack'
|
250
|
+
],
|
251
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
252
|
+
"guid#{username}",
|
253
|
+
'guidtestuser',
|
254
|
+
'guidjeff',
|
255
|
+
'guidzack'
|
256
|
+
],
|
257
|
+
},
|
258
|
+
{
|
259
|
+
'dsAttrTypeStandard:RecordName' => ['second'],
|
260
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
261
|
+
'jeff',
|
262
|
+
'zack'
|
263
|
+
],
|
264
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
265
|
+
"guid#{username}",
|
266
|
+
'guidjeff',
|
267
|
+
'guidzack'
|
268
|
+
],
|
269
|
+
},
|
270
|
+
{
|
271
|
+
'dsAttrTypeStandard:RecordName' => ['third'],
|
272
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
273
|
+
username,
|
274
|
+
'jeff',
|
275
|
+
'zack'
|
276
|
+
],
|
277
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
278
|
+
"guid#{username}",
|
279
|
+
'guidtestuser',
|
280
|
+
'guidjeff',
|
281
|
+
'guidzack'
|
282
|
+
],
|
283
|
+
}]
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
describe 'Creating a user that does not exist' do
|
288
|
+
# These are the defaults that the provider will use if a user does
|
289
|
+
# not provide a value
|
290
|
+
let(:defaults) do
|
291
|
+
{
|
292
|
+
'UniqueID' => '1000',
|
293
|
+
'RealName' => resource[:name],
|
294
|
+
'PrimaryGroupID' => '20',
|
295
|
+
'UserShell' => '/bin/bash',
|
296
|
+
'NFSHomeDirectory' => "/Users/#{resource[:name]}"
|
297
|
+
}
|
298
|
+
end
|
299
|
+
|
300
|
+
before :each do
|
301
|
+
# Stub out all calls to dscl with default values from above
|
302
|
+
defaults.each do |key, val|
|
303
|
+
provider.expects(:merge_attribute_with_dscl).with('Users', username, key, val)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Mock the rest of the dscl calls. We can't assume that our Linux
|
307
|
+
# build system will have the dscl binary
|
308
|
+
provider.expects(:create_new_user).with(username)
|
309
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'GeneratedUID').returns({'dsAttrTypeStandard:GeneratedUID' => ['GUID']})
|
310
|
+
provider.expects(:next_system_id).returns('1000')
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'should not raise any errors when creating a user with default values' do
|
314
|
+
provider.create
|
315
|
+
end
|
316
|
+
|
317
|
+
%w{password iterations salt}.each do |value|
|
318
|
+
it "should call ##{value}= if a #{value} attribute is specified" do
|
319
|
+
resource[value.intern] = 'somevalue'
|
320
|
+
setter = (value << '=').intern
|
321
|
+
provider.expects(setter).with('somevalue')
|
322
|
+
provider.create
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
it 'should merge the GroupMembership and GroupMembers dscl values if a groups attribute is specified' do
|
327
|
+
resource[:groups] = 'somegroup'
|
328
|
+
provider.expects(:merge_attribute_with_dscl).with('Groups', 'somegroup', 'GroupMembership', username)
|
329
|
+
provider.expects(:merge_attribute_with_dscl).with('Groups', 'somegroup', 'GroupMembers', 'GUID')
|
330
|
+
provider.create
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
describe 'self#instances' do
|
335
|
+
it 'should create an array of provider instances' do
|
336
|
+
provider.class.expects(:get_all_users).returns(['foo', 'bar'])
|
337
|
+
['foo', 'bar'].each do |user|
|
338
|
+
provider.class.expects(:generate_attribute_hash).with(user).returns({})
|
339
|
+
end
|
340
|
+
provider.class.instances.size.should == 2
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
describe 'self#get_all_users' do
|
345
|
+
let(:empty_plist) do
|
346
|
+
'<?xml version="1.0" encoding="UTF-8"?>
|
347
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
348
|
+
<plist version="1.0">
|
349
|
+
<dict>
|
350
|
+
</dict>
|
351
|
+
</plist>'
|
352
|
+
end
|
353
|
+
|
354
|
+
it 'should return a hash of user attributes' do
|
355
|
+
provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Users').returns(user_plist_xml)
|
356
|
+
provider.class.get_all_users.should == user_plist_hash
|
357
|
+
end
|
358
|
+
|
359
|
+
it 'should return a hash when passed an empty plist' do
|
360
|
+
provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Users').returns(empty_plist)
|
361
|
+
provider.class.get_all_users.should == {}
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
describe 'self#generate_attribute_hash' do
|
366
|
+
let(:user_plist_resource) do
|
367
|
+
{
|
368
|
+
:ensure => :present,
|
369
|
+
:provider => :directoryservice,
|
370
|
+
:groups => 'testgroup,third',
|
371
|
+
:comment => username,
|
372
|
+
:password => sha512_password_hash,
|
373
|
+
:shadowhashdata => sha512_shadowhashdata_hash,
|
374
|
+
:name => username,
|
375
|
+
:uid => 1000,
|
376
|
+
:gid => 22,
|
377
|
+
:home => user_path
|
378
|
+
}
|
379
|
+
end
|
380
|
+
|
381
|
+
before :each do
|
382
|
+
Facter.expects(:value).with(:macosx_productversion_major).twice.returns('10.7')
|
383
|
+
provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Users').returns(testuser_plist)
|
384
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'ShadowHashData').returns(sha512_shadowhashdata_hash).twice
|
385
|
+
provider.class.expects(:get_list_of_groups).returns(group_plist_hash_guid).twice
|
386
|
+
provider.class.expects(:convert_binary_to_xml).with(sha512_embedded_bplist).twice.returns(sha512_embedded_bplist_hash)
|
387
|
+
provider.class.prefetch({})
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'should return :uid values as a Fixnum' do
|
391
|
+
provider.class.generate_attribute_hash(user_plist_hash)[:uid].class.should == Fixnum
|
392
|
+
end
|
393
|
+
|
394
|
+
it 'should return :gid values as a Fixnum' do
|
395
|
+
provider.class.generate_attribute_hash(user_plist_hash)[:gid].class.should == Fixnum
|
396
|
+
end
|
397
|
+
|
398
|
+
it 'should return a hash of resource attributes' do
|
399
|
+
provider.class.generate_attribute_hash(user_plist_hash).should == user_plist_resource
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
describe '#exists?' do
|
404
|
+
# This test expects an error to be raised
|
405
|
+
# I'm PROBABLY doing this wrong...
|
406
|
+
it 'should return false if the dscl command errors out' do
|
407
|
+
provider.expects(:dscl).with('.', 'read', user_path).raises(Puppet::ExecutionFailure, 'Dscl Fails')
|
408
|
+
provider.exists?.should == false
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'should return true if the dscl command does not error' do
|
412
|
+
provider.expects(:dscl).with('.', 'read', user_path).returns(user_plist_xml)
|
413
|
+
provider.exists?.should == true
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
describe '#delete' do
|
418
|
+
it 'should call dscl when destroying/deleting a resource' do
|
419
|
+
provider.expects(:dscl).with('.', '-delete', user_path)
|
420
|
+
provider.delete
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
describe 'the groups property' do
|
425
|
+
# The below represents the result of running Plist.parse_xml on XML
|
426
|
+
# data returned from the `dscl -plist . readall /Groups` command.
|
427
|
+
# (AKA: What the get_list_of_groups method returns)
|
428
|
+
let(:group_plist_hash) do
|
429
|
+
[{
|
430
|
+
'dsAttrTypeStandard:RecordName' => ['testgroup'],
|
431
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
432
|
+
'testuser',
|
433
|
+
username,
|
434
|
+
'jeff',
|
435
|
+
'zack'
|
436
|
+
],
|
437
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
438
|
+
'guidtestuser',
|
439
|
+
'guidjeff',
|
440
|
+
'guidzack'
|
441
|
+
],
|
442
|
+
},
|
443
|
+
{
|
444
|
+
'dsAttrTypeStandard:RecordName' => ['second'],
|
445
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
446
|
+
username,
|
447
|
+
'testuser',
|
448
|
+
'jeff',
|
449
|
+
],
|
450
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
451
|
+
'guidtestuser',
|
452
|
+
'guidjeff',
|
453
|
+
],
|
454
|
+
},
|
455
|
+
{
|
456
|
+
'dsAttrTypeStandard:RecordName' => ['third'],
|
457
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
458
|
+
'jeff',
|
459
|
+
'zack'
|
460
|
+
],
|
461
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
462
|
+
'guidjeff',
|
463
|
+
'guidzack'
|
464
|
+
],
|
465
|
+
}]
|
466
|
+
end
|
467
|
+
|
468
|
+
|
469
|
+
before :each do
|
470
|
+
provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Users').returns(testuser_plist)
|
471
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'ShadowHashData').returns([])
|
472
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns('10.7')
|
473
|
+
end
|
474
|
+
|
475
|
+
it "should return a list of groups if the user's name matches GroupMembership" do
|
476
|
+
provider.class.expects(:get_list_of_groups).returns(group_plist_hash)
|
477
|
+
provider.class.prefetch({}).first.groups.should == 'second,testgroup'
|
478
|
+
end
|
479
|
+
|
480
|
+
it "should return a list of groups if the user's GUID matches GroupMembers" do
|
481
|
+
provider.class.expects(:get_list_of_groups).returns(group_plist_hash_guid)
|
482
|
+
provider.class.prefetch({}).first.groups.should == 'testgroup,third'
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
describe '#groups=' do
|
487
|
+
let(:group_plist_one_two_three) do
|
488
|
+
[{
|
489
|
+
'dsAttrTypeStandard:RecordName' => ['one'],
|
490
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
491
|
+
'jeff',
|
492
|
+
'zack'
|
493
|
+
],
|
494
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
495
|
+
'guidjeff',
|
496
|
+
'guidzack'
|
497
|
+
],
|
498
|
+
},
|
499
|
+
{
|
500
|
+
'dsAttrTypeStandard:RecordName' => ['two'],
|
501
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
502
|
+
'jeff',
|
503
|
+
'zack',
|
504
|
+
username
|
505
|
+
],
|
506
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
507
|
+
'guidjeff',
|
508
|
+
'guidzack'
|
509
|
+
],
|
510
|
+
},
|
511
|
+
{
|
512
|
+
'dsAttrTypeStandard:RecordName' => ['three'],
|
513
|
+
'dsAttrTypeStandard:GroupMembership' => [
|
514
|
+
'jeff',
|
515
|
+
'zack',
|
516
|
+
username
|
517
|
+
],
|
518
|
+
'dsAttrTypeStandard:GroupMembers' => [
|
519
|
+
'guidjeff',
|
520
|
+
'guidzack'
|
521
|
+
],
|
522
|
+
}]
|
523
|
+
end
|
524
|
+
|
525
|
+
before :each do
|
526
|
+
provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Users').returns(testuser_plist)
|
527
|
+
provider.class.expects(:get_list_of_groups).returns(group_plist_one_two_three)
|
528
|
+
end
|
529
|
+
|
530
|
+
it 'should call dscl to add necessary groups' do
|
531
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns('10.7')
|
532
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'ShadowHashData').returns([])
|
533
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'GeneratedUID').returns({'dsAttrTypeStandard:GeneratedUID' => ['guidnonexistant_user']})
|
534
|
+
provider.expects(:groups).returns('two,three')
|
535
|
+
provider.expects(:dscl).with('.', '-merge', '/Groups/one', 'GroupMembership', 'nonexistant_user')
|
536
|
+
provider.expects(:dscl).with('.', '-merge', '/Groups/one', 'GroupMembers', 'guidnonexistant_user')
|
537
|
+
provider.class.prefetch({})
|
538
|
+
provider.groups= 'one,two,three'
|
539
|
+
end
|
540
|
+
|
541
|
+
#describe how passwords are fetched in 10.5 and 10.6
|
542
|
+
['10.5', '10.6'].each do |os_ver|
|
543
|
+
it "should call the get_sha1 method on #{os_ver}" do
|
544
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns(os_ver)
|
545
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'ShadowHashData').returns([])
|
546
|
+
provider.class.expects(:get_sha1).with('0A7D5B63-3AD4-4CA7-B03E-85876F1D1FB3').returns('password')
|
547
|
+
provider.class.prefetch({}).first.password.should == 'password'
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
it 'should call the get_salted_sha512 method on 10.7 and return the correct hash' do
|
552
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns('10.7')
|
553
|
+
provider.class.expects(:convert_binary_to_xml).with(sha512_embedded_bplist).returns(sha512_embedded_bplist_hash)
|
554
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'ShadowHashData').returns(sha512_shadowhashdata_hash)
|
555
|
+
provider.class.prefetch({}).first.password.should == sha512_password_hash
|
556
|
+
end
|
557
|
+
|
558
|
+
it 'should call the get_salted_sha512_pbkdf2 method on 10.8 and return the correct hash' do
|
559
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns('10.8')
|
560
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username,'ShadowHashData').returns(pbkdf2_shadowhashdata_hash)
|
561
|
+
provider.class.expects(:convert_binary_to_xml).with(pbkdf2_embedded_plist).returns(pbkdf2_embedded_bplist_hash)
|
562
|
+
provider.class.prefetch({}).first.password.should == pbkdf2_password_hash
|
563
|
+
end
|
564
|
+
|
565
|
+
end
|
566
|
+
|
567
|
+
describe '#password=' do
|
568
|
+
['10.5', '10.6'].each do |os_ver|
|
569
|
+
it "should call write_sha1_hash when setting the password on #{os_ver}" do
|
570
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns(os_ver)
|
571
|
+
provider.expects(:write_sha1_hash).with('password')
|
572
|
+
provider.password = 'password'
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
it 'should call write_password_to_users_plist when setting the password on 10.7' do
|
577
|
+
Facter.expects(:value).with(:macosx_productversion_major).twice.returns('10.7')
|
578
|
+
provider.expects(:write_password_to_users_plist).with(sha512_password_hash)
|
579
|
+
provider.expects(:flush_dscl_cache).twice
|
580
|
+
provider.password = sha512_password_hash
|
581
|
+
end
|
582
|
+
|
583
|
+
it 'should call write_password_to_users_plist when setting the password on 10.8' do
|
584
|
+
Facter.expects(:value).with(:macosx_productversion_major).twice.returns('10.8')
|
585
|
+
provider.expects(:write_password_to_users_plist).with(pbkdf2_password_hash)
|
586
|
+
provider.expects(:flush_dscl_cache).twice
|
587
|
+
provider.password = pbkdf2_password_hash
|
588
|
+
end
|
589
|
+
|
590
|
+
it "should raise an error on 10.7 if a password hash that doesn't contain 136 characters is passed" do
|
591
|
+
Facter.expects(:value).with(:macosx_productversion_major).twice.returns('10.7')
|
592
|
+
expect { provider.password = 'password' }.to raise_error Puppet::Error, /OS X 10\.7 requires a Salted SHA512 hash password of 136 characters\. Please check your password and try again/
|
593
|
+
end
|
594
|
+
|
595
|
+
it "should raise an error on 10.8 if a password hash that doesn't contain 256 characters is passed" do
|
596
|
+
Facter.expects(:value).with(:macosx_productversion_major).twice.returns('10.8')
|
597
|
+
expect { provider.password = 'password' }.to raise_error Puppet::Error, /OS X versions > 10\.7 require a Salted SHA512 PBKDF2 password hash of 256 characters\. Please check your password and try again\./
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
describe '#get_list_of_groups' do
|
602
|
+
# The below value is the result of running `dscl -plist . readall /Groups`
|
603
|
+
# on an OS X system.
|
604
|
+
let(:groups_xml) do
|
605
|
+
'<?xml version="1.0" encoding="UTF-8"?>
|
606
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
607
|
+
<plist version="1.0">
|
608
|
+
<array>
|
609
|
+
<dict>
|
610
|
+
<key>dsAttrTypeStandard:AppleMetaNodeLocation</key>
|
611
|
+
<array>
|
612
|
+
<string>/Local/Default</string>
|
613
|
+
</array>
|
614
|
+
<key>dsAttrTypeStandard:GeneratedUID</key>
|
615
|
+
<array>
|
616
|
+
<string>ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000053</string>
|
617
|
+
</array>
|
618
|
+
<key>dsAttrTypeStandard:Password</key>
|
619
|
+
<array>
|
620
|
+
<string>*</string>
|
621
|
+
</array>
|
622
|
+
<key>dsAttrTypeStandard:PrimaryGroupID</key>
|
623
|
+
<array>
|
624
|
+
<string>83</string>
|
625
|
+
</array>
|
626
|
+
<key>dsAttrTypeStandard:RealName</key>
|
627
|
+
<array>
|
628
|
+
<string>SPAM Assassin Group 2</string>
|
629
|
+
</array>
|
630
|
+
<key>dsAttrTypeStandard:RecordName</key>
|
631
|
+
<array>
|
632
|
+
<string>_amavisd</string>
|
633
|
+
<string>amavisd</string>
|
634
|
+
</array>
|
635
|
+
<key>dsAttrTypeStandard:RecordType</key>
|
636
|
+
<array>
|
637
|
+
<string>dsRecTypeStandard:Groups</string>
|
638
|
+
</array>
|
639
|
+
</dict>
|
640
|
+
</array>
|
641
|
+
</plist>'
|
642
|
+
end
|
643
|
+
|
644
|
+
# The below value is the result of executing Plist.parse_xml on
|
645
|
+
# groups_xml
|
646
|
+
let(:groups_hash) do
|
647
|
+
[{ 'dsAttrTypeStandard:AppleMetaNodeLocation' => ['/Local/Default'],
|
648
|
+
'dsAttrTypeStandard:GeneratedUID' => ['ABCDEFAB-CDEF-ABCD-EFAB-CDEF00000053'],
|
649
|
+
'dsAttrTypeStandard:Password' => ['*'],
|
650
|
+
'dsAttrTypeStandard:PrimaryGroupID' => ['83'],
|
651
|
+
'dsAttrTypeStandard:RealName' => ['SPAM Assassin Group 2'],
|
652
|
+
'dsAttrTypeStandard:RecordName' => ['_amavisd', 'amavisd'],
|
653
|
+
'dsAttrTypeStandard:RecordType' => ['dsRecTypeStandard:Groups']
|
654
|
+
}]
|
655
|
+
end
|
656
|
+
|
657
|
+
it 'should return a array of hashes containing group data' do
|
658
|
+
provider.class.expects(:dscl).with('-plist', '.', 'readall', '/Groups').returns(groups_xml)
|
659
|
+
provider.class.get_list_of_groups.should == groups_hash
|
660
|
+
end
|
661
|
+
end
|
662
|
+
|
663
|
+
describe '#get_attribute_from_dscl' do
|
664
|
+
# The below value is the result of executing
|
665
|
+
# `dscl -plist . read /Users/<username/ GeneratedUID`
|
666
|
+
# on an OS X system.
|
667
|
+
let(:user_guid_xml) do
|
668
|
+
'<?xml version="1.0" encoding="UTF-8"?>
|
669
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
670
|
+
<plist version="1.0">
|
671
|
+
<dict>
|
672
|
+
<key>dsAttrTypeStandard:GeneratedUID</key>
|
673
|
+
<array>
|
674
|
+
<string>DCC660C6-F5A9-446D-B9FF-3C0258AB5BA0</string>
|
675
|
+
</array>
|
676
|
+
</dict>
|
677
|
+
</plist>'
|
678
|
+
end
|
679
|
+
|
680
|
+
# The below value is the result of parsing user_guid_xml with
|
681
|
+
# Plist.parse_xml
|
682
|
+
let(:user_guid_hash) do
|
683
|
+
{ 'dsAttrTypeStandard:GeneratedUID' => ['DCC660C6-F5A9-446D-B9FF-3C0258AB5BA0'] }
|
684
|
+
end
|
685
|
+
|
686
|
+
it 'should return a hash containing a user\'s dscl attribute data' do
|
687
|
+
provider.class.expects(:dscl).with('-plist', '.', 'read', user_path, 'GeneratedUID').returns(user_guid_xml)
|
688
|
+
provider.class.get_attribute_from_dscl('Users', username, 'GeneratedUID').should == user_guid_hash
|
689
|
+
end
|
690
|
+
end
|
691
|
+
|
692
|
+
describe '#convert_xml_to_binary' do
|
693
|
+
# Because this method relies on a binary that only exists on OS X, a stub
|
694
|
+
# object is needed to expect the calls. This makes testing somewhat...uneventful
|
695
|
+
let(:stub_io_object) { stub('connection') }
|
696
|
+
|
697
|
+
it 'should use plutil to successfully convert an xml plist to a binary plist' do
|
698
|
+
IO.expects(:popen).with('plutil -convert binary1 -o - -', 'r+').yields stub_io_object
|
699
|
+
Plist::Emit.expects(:dump).with('ruby_hash').returns('xml_plist_data')
|
700
|
+
stub_io_object.expects(:write).with('xml_plist_data')
|
701
|
+
stub_io_object.expects(:close_write)
|
702
|
+
stub_io_object.expects(:read).returns('binary_plist_data')
|
703
|
+
provider.class.convert_xml_to_binary('ruby_hash').should == 'binary_plist_data'
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
describe '#convert_binary_to_xml' do
|
708
|
+
let(:stub_io_object) { stub('connection') }
|
709
|
+
|
710
|
+
it 'should accept a binary plist and return a ruby hash containing the plist data' do
|
711
|
+
IO.expects(:popen).with('plutil -convert xml1 -o - -', 'r+').yields stub_io_object
|
712
|
+
stub_io_object.expects(:write).with('binary_plist_data')
|
713
|
+
stub_io_object.expects(:close_write)
|
714
|
+
stub_io_object.expects(:read).returns(user_plist_xml)
|
715
|
+
provider.class.convert_binary_to_xml('binary_plist_data').should == user_plist_hash
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
describe '#next_system_id' do
|
720
|
+
it 'should return the next available UID number that is not in the list obtained from dscl and is greater than the passed integer value' do
|
721
|
+
provider.expects(:dscl).with('.', '-list', '/Users', 'uid').returns("kathee 312\ngary 11\ntanny 33\njohn 9\nzach 5")
|
722
|
+
provider.next_system_id(30).should == 34
|
723
|
+
end
|
724
|
+
end
|
725
|
+
|
726
|
+
describe '#get_salted_sha512' do
|
727
|
+
it "should accept a hash whose 'SALTED-SHA512' key contains a StringIO object with a base64 encoded salted-SHA512 password hash and return the hex value of that password hash" do
|
728
|
+
provider.class.get_salted_sha512(sha512_embedded_bplist_hash).should == sha512_password_hash
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
describe '#get_salted_sha512_pbkdf2' do
|
733
|
+
it "should accept a hash containing a PBKDF2 password hash, salt, and iterations value and return the correct password hash" do
|
734
|
+
provider.class.get_salted_sha512_pbkdf2('entropy', pbkdf2_embedded_bplist_hash).should == pbkdf2_password_hash
|
735
|
+
end
|
736
|
+
it "should accept a hash containing a PBKDF2 password hash, salt, and iterations value and return the correct salt value" do
|
737
|
+
provider.class.get_salted_sha512_pbkdf2('salt', pbkdf2_embedded_bplist_hash).should == pbkdf2_salt_value
|
738
|
+
end
|
739
|
+
it "should accept a hash containing a PBKDF2 password hash, salt, and iterations value and return the correct iterations value" do
|
740
|
+
provider.class.get_salted_sha512_pbkdf2('iterations', pbkdf2_embedded_bplist_hash).should == pbkdf2_iterations_value
|
741
|
+
end
|
742
|
+
it "should return a Fixnum value when looking up the PBKDF2 iterations value" do
|
743
|
+
provider.class.get_salted_sha512_pbkdf2('iterations', pbkdf2_embedded_bplist_hash).class.should == Fixnum
|
744
|
+
end
|
745
|
+
it "should raise an error if a field other than 'entropy', 'salt', or 'iterations' is passed" do
|
746
|
+
expect { provider.class.get_salted_sha512_pbkdf2('othervalue', pbkdf2_embedded_bplist_hash) }.to raise_error Puppet::Error, /Puppet has tried to read an incorrect value from the SALTED-SHA512-PBKDF2 hash. Acceptable fields are 'salt', 'entropy', or 'iterations'/
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
describe '#get_sha1' do
|
751
|
+
let(:password_hash_file) { '/var/db/shadow/hash/user_guid' }
|
752
|
+
let(:stub_password_file) { stub('connection') }
|
753
|
+
|
754
|
+
it 'should return a a sha1 hash read from disk' do
|
755
|
+
File.expects(:exists?).with(password_hash_file).returns(true)
|
756
|
+
File.expects(:file?).with(password_hash_file).returns(true)
|
757
|
+
File.expects(:readable?).with(password_hash_file).returns(true)
|
758
|
+
File.expects(:new).with(password_hash_file).returns(stub_password_file)
|
759
|
+
stub_password_file.expects(:read).returns('sha1_password_hash')
|
760
|
+
stub_password_file.expects(:close)
|
761
|
+
provider.class.get_sha1('user_guid').should == 'sha1_password_hash'
|
762
|
+
end
|
763
|
+
|
764
|
+
it 'should return nil if the password_hash_file does not exist' do
|
765
|
+
File.expects(:exists?).with(password_hash_file).returns(false)
|
766
|
+
provider.class.get_sha1('user_guid').should == nil
|
767
|
+
end
|
768
|
+
|
769
|
+
it 'should return nil if the password_hash_file is not a file' do
|
770
|
+
File.expects(:exists?).with(password_hash_file).returns(true)
|
771
|
+
File.expects(:file?).with(password_hash_file).returns(false)
|
772
|
+
provider.class.get_sha1('user_guid').should == nil
|
773
|
+
end
|
774
|
+
|
775
|
+
it 'should raise an error if the password_hash_file is not readable' do
|
776
|
+
File.expects(:exists?).with(password_hash_file).returns(true)
|
777
|
+
File.expects(:file?).with(password_hash_file).returns(true)
|
778
|
+
File.expects(:readable?).with(password_hash_file).returns(false)
|
779
|
+
expect { provider.class.get_sha1('user_guid').should == nil }.to raise_error Puppet::Error, /Could not read password hash file at #{password_hash_file}/
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
describe '#write_password_to_users_plist' do
|
784
|
+
let(:sha512_plist_xml) do
|
785
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>KerberosKeys</key>\n\t<array>\n\t\t<data>\n\t\tMIIBS6EDAgEBoIIBQjCCAT4wcKErMCmgAwIBEqEiBCCS/0Im7BAps/YhX/ED\n\t\tKOpDeSMFkUsu3UzEa6gqDu35BKJBMD+gAwIBA6E4BDZMS0RDOlNIQTEuNDM4\n\t\tM0UxNTJEOUQzOTRBQTMyRDEzQUU5OEY2RjZFMUZFOEQwMEY4MWplZmYwYKEb\n\t\tMBmgAwIBEaESBBAk8a3rrFk5mHAdEU5nRgFwokEwP6ADAgEDoTgENkxLREM6\n\t\tU0hBMS40MzgzRTE1MkQ5RDM5NEFBMzJEMTNBRTk4RjZGNkUxRkU4RDAwRjgx\n\t\tamVmZjBooSMwIaADAgEQoRoEGFg71irsV+9ddRNPSn9houo3Q6jZuj55XaJB\n\t\tMD+gAwIBA6E4BDZMS0RDOlNIQTEuNDM4M0UxNTJEOUQzOTRBQTMyRDEzQUU5\n\t\tOEY2RjZFMUZFOEQwMEY4MWplZmY=\n\t\t</data>\n\t</array>\n\t<key>ShadowHashData</key>\n\t<array>\n\t\t<data>\n\t\tYnBsaXN0MDDRAQJdU0FMVEVELVNIQTUxMk8QRFNL0iuruijP6becUWe43GTX\n\t\t5WTgOTi2emx41DMnwnB4vbKieVOE4eNHiyocX5c0GX1LWJ6VlZqZ9EnDLsuA\n\t\tNC5Ga9qlCAsZAAAAAAAAAQEAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAGA=\n\t\t</data>\n\t</array>\n\t<key>authentication_authority</key>\n\t<array>\n\t\t<string>;Kerberosv5;;jeff@LKDC:SHA1.4383E152D9D394AA32D13AE98F6F6E1FE8D00F81;LKDC:SHA1.4383E152D9D394AA32D13AE98F6F6E1FE8D00F81</string>\n\t\t<string>;ShadowHash;HASHLIST:<SALTED-SHA512></string>\n\t</array>\n\t<key>dsAttrTypeStandard:ShadowHashData</key>\n\t<array>\n\t\t<data>\n\t\tYnBsaXN0MDDRAQJdU0FMVEVELVNIQTUxMk8QRH6n1ZITH1eyyPi9vOyNnfEh\n\t\tKKOGOTpPAMdhm6wmIqRNRRQZ0R2lEtWRWrmOOXGKyUCD/i79a/cQpU1Hf4/3\n\t\tNbElhxktCAsZAAAAAAAAAQEAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAGA=\n\t\t</data>\n\t</array>\n\t<key>generateduid</key>\n\t<array>\n\t\t<string>3AC74939-C14F-45DD-B6A9-D1A82373F0B0</string>\n\t</array>\n\t<key>name</key>\n\t<array>\n\t\t<string>jeff</string>\n\t</array>\n\t<key>passwd</key>\n\t<array>\n\t\t<string>********</string>\n\t</array>\n\t<key>passwordpolicyoptions</key>\n\t<array>\n\t\t<data>\n\t\tPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU\n\t\tWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO\n\t\tIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w\n\t\tLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp\n\t\tbGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr\n\t\tZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt\n\t\tMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8\n\t\tL2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtl\n\t\teT5wYXNzd29yZFRpbWVzdGFtcDwva2V5PgoJPGRhdGU+MjAxMi0wOC0xMVQw\n\t\tMDozNTo1MFo8L2RhdGU+CjwvZGljdD4KPC9wbGlzdD4K\n\t\t</data>\n\t</array>\n\t<key>uid</key>\n\t<array>\n\t\t<string>28</string>\n\t</array>\n</dict>\n</plist>"
|
786
|
+
end
|
787
|
+
|
788
|
+
let(:pbkdf2_plist_xml) do
|
789
|
+
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>KerberosKeys</key>\n\t<array>\n\t\t<data>\n\t\tMIIBS6EDAgEBoIIBQjCCAT4wcKErMCmgAwIBEqEiBCDrboPy0gxu7oTZR/Pc\n\t\tYdCBC9ivXo1k05gt036/aNe5VqJBMD+gAwIBA6E4BDZMS0RDOlNIQTEuNDEz\n\t\tQTMwRjU5MEVFREM3ODdENTMyOTgxODUwQTk3NTI0NUIwQTcyM2plZmYwYKEb\n\t\tMBmgAwIBEaESBBCm02SYYdsxo2fiDP4KuPtmokEwP6ADAgEDoTgENkxLREM6\n\t\tU0hBMS40MTNBMzBGNTkwRUVEQzc4N0Q1MzI5ODE4NTBBOTc1MjQ1QjBBNzIz\n\t\tamVmZjBooSMwIaADAgEQoRoEGHPBc7Dg7zjaE8g+YXObwupiBLMIlCrN5aJB\n\t\tMD+gAwIBA6E4BDZMS0RDOlNIQTEuNDEzQTMwRjU5MEVFREM3ODdENTMyOTgx\n\t\tODUwQTk3NTI0NUIwQTcyM2plZmY=\n\t\t</data>\n\t</array>\n\t<key>ShadowHashData</key>\n\t<array>\n\t\t<data>\n\t\tYnBsaXN0MDDRAQJfEBRTQUxURUQtU0hBNTEyLVBCS0RGMtMDBAUGBwhXZW50\n\t\tcm9weVRzYWx0Wml0ZXJhdGlvbnNPEIAFkK3hnmlTwTWuhyrndhgjXffUbGPe\n\t\tf5oPzfLNnn2F5LfKhoEBI1thWOBaMJgF7kgUsCekvpwj7CkmvIFyJpr/ulya\n\t\tWYXoEJH6aJgHbSl/H6p1+mF1Ue8WcddSAFXEoNl7m5xYBaoyK67bzY7pxSOB\n\t\tFlOsLqnpyNjxrFGaDytZXk8QIJN3xGkIocisLD5FwNRNqK0PzYXsXBTZpZ/8\n\t\tQMnaMfDsEWCwCAsiKTE2QcTnAAAAAAAAAQEAAAAAAAAACQAAAAAAAAAAAAAA\n\t\tAAAAAOo=\n\t\t</data>\n\t</array>\n\t<key>authentication_authority</key>\n\t<array>\n\t\t<string>;Kerberosv5;;jeff@LKDC:SHA1.413A30F590EEDC787D532981850A975245B0A723;LKDC:SHA1.413A30F590EEDC787D532981850A975245B0A723</string>\n\t\t<string>;ShadowHash;HASHLIST:<SALTED-SHA512-PBKDF2></string>\n\t</array>\n\t<key>generateduid</key>\n\t<array>\n\t\t<string>1CB825D1-2DF7-43CC-B874-DB6BBB76C402</string>\n\t</array>\n\t<key>gid</key>\n\t<array>\n\t\t<string>21</string>\n\t</array>\n\t<key>name</key>\n\t<array>\n\t\t<string>jeff</string>\n\t</array>\n\t<key>passwd</key>\n\t<array>\n\t\t<string>********</string>\n\t</array>\n\t<key>passwordpolicyoptions</key>\n\t<array>\n\t\t<data>\n\t\tPD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NU\n\t\tWVBFIHBsaXN0IFBVQkxJQyAiLS8vQXBwbGUvL0RURCBQTElTVCAxLjAvL0VO\n\t\tIiAiaHR0cDovL3d3dy5hcHBsZS5jb20vRFREcy9Qcm9wZXJ0eUxpc3QtMS4w\n\t\tLmR0ZCI+CjxwbGlzdCB2ZXJzaW9uPSIxLjAiPgo8ZGljdD4KCTxrZXk+ZmFp\n\t\tbGVkTG9naW5Db3VudDwva2V5PgoJPGludGVnZXI+MDwvaW50ZWdlcj4KCTxr\n\t\tZXk+ZmFpbGVkTG9naW5UaW1lc3RhbXA8L2tleT4KCTxkYXRlPjIwMDEtMDEt\n\t\tMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtleT5sYXN0TG9naW5UaW1lc3RhbXA8\n\t\tL2tleT4KCTxkYXRlPjIwMDEtMDEtMDFUMDA6MDA6MDBaPC9kYXRlPgoJPGtl\n\t\teT5wYXNzd29yZExhc3RTZXRUaW1lPC9rZXk+Cgk8ZGF0ZT4yMDEyLTA3LTI1\n\t\tVDE4OjQ3OjU5WjwvZGF0ZT4KPC9kaWN0Pgo8L3BsaXN0Pgo=\n\t\t</data>\n\t</array>\n\t<key>uid</key>\n\t<array>\n\t\t<string>28</string>\n\t</array>\n</dict>\n</plist>"
|
790
|
+
end
|
791
|
+
|
792
|
+
let(:sha512_shadowhashdata) do
|
793
|
+
{
|
794
|
+
'SALTED-SHA512' => StringIO.new('blankvalue')
|
795
|
+
}
|
796
|
+
end
|
797
|
+
|
798
|
+
let(:pbkdf2_shadowhashdata) do
|
799
|
+
{
|
800
|
+
'SALTED-SHA512-PBKDF2' => {
|
801
|
+
'entropy' => StringIO.new('blank_entropy'),
|
802
|
+
'salt' => StringIO.new('blank_salt'),
|
803
|
+
'iterations' => 100
|
804
|
+
}
|
805
|
+
}
|
806
|
+
end
|
807
|
+
|
808
|
+
let(:stub_shadowhashdata) { stub('connection') }
|
809
|
+
|
810
|
+
it 'should call set_salted_sha512 on 10.7 when given a a salted-SHA512 password hash' do
|
811
|
+
provider.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', "#{users_plist_dir}/nonexistant_user.plist").returns(sha512_plist_xml)
|
812
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns('10.7')
|
813
|
+
# The below line is not as tight as I would like. It would be
|
814
|
+
# nice to set the expectation using .with and passing the hash
|
815
|
+
# we're expecting, but there are several StringIO objects that
|
816
|
+
# report with a hex identifier. Even though the string data
|
817
|
+
# matches, frequently the hex identifiers vary slightly. I
|
818
|
+
# feel like the work I'd need to do to keep the StringIO objects
|
819
|
+
# in sync would result in a test with staged data.
|
820
|
+
provider.expects(:set_salted_sha512)
|
821
|
+
provider.class.expects(:convert_binary_to_xml).returns(sha512_embedded_bplist_hash)
|
822
|
+
provider.write_password_to_users_plist(sha512_password_hash)
|
823
|
+
end
|
824
|
+
|
825
|
+
it 'should call set_salted_pbkdf2 on 10.8 when given a PBKDF2 password hash' do
|
826
|
+
provider.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', "#{users_plist_dir}/nonexistant_user.plist").returns(pbkdf2_plist_xml)
|
827
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns('10.8')
|
828
|
+
# See comment in previous test...
|
829
|
+
provider.expects(:set_salted_pbkdf2)
|
830
|
+
provider.class.expects(:convert_binary_to_xml).returns(pbkdf2_embedded_bplist_hash)
|
831
|
+
provider.write_password_to_users_plist(pbkdf2_password_hash)
|
832
|
+
end
|
833
|
+
|
834
|
+
it "should delete the SALTED-SHA512 key in the shadow_hash_data hash if it exists on a 10.8 system and write_password_to_users_plist has been called to set the user's password" do
|
835
|
+
provider.expects(:plutil).with('-convert', 'xml1', '-o', '/dev/stdout', "#{users_plist_dir}/nonexistant_user.plist").returns('xml_data')
|
836
|
+
Plist.expects(:parse_xml).with('xml_data').returns('ruby_hash')
|
837
|
+
Facter.expects(:value).with(:macosx_productversion_major).returns('10.8')
|
838
|
+
provider.expects(:get_shadow_hash_data).with('ruby_hash').returns(stub_shadowhashdata)
|
839
|
+
stub_shadowhashdata.expects(:[]).with('SALTED-SHA512').returns(true)
|
840
|
+
stub_shadowhashdata.expects(:delete).with('SALTED-SHA512')
|
841
|
+
provider.expects(:set_salted_pbkdf2).with('ruby_hash', stub_shadowhashdata, 'entropy', pbkdf2_password_hash)
|
842
|
+
provider.write_password_to_users_plist(pbkdf2_password_hash)
|
843
|
+
end
|
844
|
+
end
|
845
|
+
|
846
|
+
describe '#set_salted_sha512' do
|
847
|
+
let(:users_plist) { {'ShadowHashData' => [StringIO.new('string_data')] } }
|
848
|
+
let(:converted_string) { "fqfVkhMfV7LI+L287I2d8SEoo4Y5Ok8Ax2GbrCYipE1FFBnRHaUS1ZFauY45\ncYrJQIP+Lv1r9xClTUd/j/c1sSWHGS0=" }
|
849
|
+
|
850
|
+
it 'should set the SALTED-SHA512 password hash for a user in 10.7 and call the write_users_plist_to_disk method to write the plist to disk' do
|
851
|
+
Hash.expects(:new).never
|
852
|
+
Base64.expects(:decode64).with(converted_string).returns(sha512_pw_string)
|
853
|
+
provider.class.expects(:convert_xml_to_binary).with(sha512_embedded_bplist_hash).returns(sha512_embedded_bplist)
|
854
|
+
# Again, here's another test that's loose because of StringIO objects...
|
855
|
+
provider.expects(:write_users_plist_to_disk)
|
856
|
+
provider.set_salted_sha512(users_plist, sha512_embedded_bplist_hash, sha512_password_hash)
|
857
|
+
end
|
858
|
+
|
859
|
+
it 'should set the salted-SHA512 password, even if a blank shadow_hash_data hash is passed' do
|
860
|
+
# The only thing that sets this aside from the previous test is the
|
861
|
+
# Hash.new call that's expected if a shadow_hash_data argument is
|
862
|
+
# passed that doesn't have a 'SALTED-SHA512' key.
|
863
|
+
Hash.expects(:new).returns({})
|
864
|
+
Base64.expects(:decode64).with(converted_string).returns(sha512_pw_string)
|
865
|
+
provider.class.expects(:convert_xml_to_binary).returns(sha512_embedded_bplist)
|
866
|
+
provider.expects(:write_users_plist_to_disk)
|
867
|
+
provider.set_salted_sha512(users_plist, false, sha512_password_hash)
|
868
|
+
end
|
869
|
+
end
|
870
|
+
|
871
|
+
describe '#set_salted_pbkdf2' do
|
872
|
+
let(:users_plist) { {'ShadowHashData' => [StringIO.new('string_data')] } }
|
873
|
+
|
874
|
+
# The below are the result of running "[[value].pack("H*")].pack("m").strip"
|
875
|
+
# where value is a hex string passed by pbkdf2_password_hash and
|
876
|
+
# pbkdf2_salt_value
|
877
|
+
let(:converted_pw_string) { "BZCt4Z5pU8E1rocq53YYI1331Gxj3n+aD83yzZ59heS3yoaBASNbYVjgWjCY\nBe5IFLAnpL6cI+wpJryBciaa/7pcmlmF6BCR+miYB20pfx+qdfphdVHvFnHX\nUgBVxKDZe5ucWAWqMiuu282O6cUjgRZTrC6p6cjY8axRmg8rWV4=" }
|
878
|
+
let(:converted_salt_string) { "k3fEaQihyKwsPkXA1E2orQ/NhexcFNmln/xAydox8Ow=" }
|
879
|
+
|
880
|
+
it "should set the PBKDF2 password hash when the 'entropy' field is passed with a valid password hash" do
|
881
|
+
Base64.expects(:decode64).with(converted_pw_string).returns(pbkdf2_pw_string)
|
882
|
+
provider.class.expects(:convert_xml_to_binary).returns(pbkdf2_embedded_plist)
|
883
|
+
provider.expects(:write_users_plist_to_disk)
|
884
|
+
users_plist.expects(:[]=).with('passwd', '********')
|
885
|
+
provider.set_salted_pbkdf2(users_plist, pbkdf2_embedded_bplist_hash, 'entropy', pbkdf2_password_hash)
|
886
|
+
end
|
887
|
+
|
888
|
+
it "should set the PBKDF2 password hash when the 'salt' field is passed with a valid password hash" do
|
889
|
+
Base64.expects(:decode64).with(converted_salt_string).returns(pbkdf2_salt_string)
|
890
|
+
provider.class.expects(:convert_xml_to_binary).returns(pbkdf2_embedded_plist)
|
891
|
+
provider.expects(:write_users_plist_to_disk)
|
892
|
+
users_plist.expects(:[]=).with('passwd', '********')
|
893
|
+
provider.set_salted_pbkdf2(users_plist, pbkdf2_embedded_bplist_hash, 'salt', pbkdf2_salt_value)
|
894
|
+
end
|
895
|
+
|
896
|
+
it "should set the PBKDF2 password hash when the 'iterations' field is passed with a valid password hash" do
|
897
|
+
provider.class.expects(:convert_xml_to_binary).returns(pbkdf2_embedded_plist)
|
898
|
+
provider.expects(:write_users_plist_to_disk)
|
899
|
+
users_plist.expects(:[]=).with('passwd', '********')
|
900
|
+
provider.set_salted_pbkdf2(users_plist, pbkdf2_embedded_bplist_hash, 'iterations', pbkdf2_iterations_value)
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
describe '#write_users_plist_to_disk' do
|
905
|
+
it 'should save the passed plist to disk and convert it to a binary plist' do
|
906
|
+
Plist::Emit.expects(:save_plist).with(user_plist_xml, "#{users_plist_dir}/nonexistant_user.plist")
|
907
|
+
provider.expects(:plutil).with('-convert', 'binary1', "#{users_plist_dir}/nonexistant_user.plist")
|
908
|
+
provider.write_users_plist_to_disk(user_plist_xml)
|
909
|
+
end
|
910
|
+
end
|
911
|
+
|
912
|
+
describe '#write_sha1_hash' do
|
913
|
+
let(:password_hash_dir) { '/var/db/shadow/hash' }
|
914
|
+
|
915
|
+
it "should write the sha1 hash to a file on disk named after the user's GUID and also ensure that ':ShadowHash;' is included in the user's AuthenticationAuthority" do
|
916
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'GeneratedUID').returns({'dsAttrTypeStandard:GeneratedUID' => ['GUID']})
|
917
|
+
provider.expects(:write_to_file).with("#{password_hash_dir}/GUID", 'sha1_password')
|
918
|
+
provider.expects(:dscl).with('.', '-merge', user_path, 'AuthenticationAuthority', ';ShadowHash;').returns(true)
|
919
|
+
provider.write_sha1_hash('sha1_password')
|
920
|
+
end
|
921
|
+
|
922
|
+
it "should raise an error if Puppet cannot write to the file in /var/db/shadow/hash named after the user's GUID" do
|
923
|
+
File.expects(:open).with('filename', 'w').raises(Errno::EACCES, 'boom')
|
924
|
+
expect { provider.write_to_file('filename', 'sha1_password') }.to raise_error Puppet::Error, /Could not write to file filename: Permission denied - boom/
|
925
|
+
end
|
926
|
+
|
927
|
+
it "should raise an error if dscl cannot merge ';ShadowHash;' into the user's AuthenticationAuthority" do
|
928
|
+
provider.class.expects(:get_attribute_from_dscl).with('Users', username, 'GeneratedUID').returns({'dsAttrTypeStandard:GeneratedUID' => ['GUID']})
|
929
|
+
provider.expects(:write_to_file).with("#{password_hash_dir}/GUID", 'sha1_password')
|
930
|
+
provider.expects(:dscl).with('.', '-merge', user_path, 'AuthenticationAuthority', ';ShadowHash;').raises(Puppet::ExecutionFailure, 'boom')
|
931
|
+
expect { provider.write_sha1_hash('sha1_password') }.to raise_error Puppet::Error, /Could not set the dscl AuthenticationAuthority key with value: ;ShadowHash;/
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
describe '#merge_attribute_with_dscl' do
|
936
|
+
it 'should raise an error if a dscl command raises an error' do
|
937
|
+
provider.expects(:dscl).with('.', '-merge', user_path, 'GeneratedUID', 'GUID').raises(Puppet::ExecutionFailure, 'boom')
|
938
|
+
expect { provider.merge_attribute_with_dscl('Users', username, 'GeneratedUID', 'GUID') }.to raise_error Puppet::Error, /Could not set the dscl GeneratedUID key with value: GUID/
|
939
|
+
end
|
940
|
+
end
|
941
|
+
end
|
942
|
+
|
943
|
+
|