-
Notifications
You must be signed in to change notification settings - Fork 49
/
Copy pathmacos_user.rb
262 lines (226 loc) · 7.55 KB
/
macos_user.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
unified_mode true
provides :macos_user
default_action :create
property :username, String, name_property: true
property :password, String, sensitive: true
property :autologin, [TrueClass]
property :admin, [TrueClass]
property :fullname, String
property :groups, [Array, String]
property :hidden, [true, false], default: false
property :secure_token, [true, false], default: false
property :existing_token_auth, Hash
action_class do
def user_home
::File.join('/', 'Users', new_resource.username)
end
def user_hidden_home
::File.join('/', 'var', new_resource.username)
end
def user_sharepoints
::File.join('/', 'SharePoints', new_resource.username)
end
def setup_assistant_plist
::File.join(user_home, 'Library', 'Preferences', 'com.apple.SetupAssistant.plist')
end
def login_window_plist
::File.join('/', 'Library', 'Preferences', 'com.apple.loginwindow.plist')
end
def setup_assistant_keypair_values
{ 'DidSeeCloudSetup' => true,
'DidSeeSiriSetup' => true,
'DidSeePrivacy' => true,
'DidSeeAccessibility' => true,
'DidSeeAppearanceSetup' => true,
'DidSeeScreenTime' => true,
'DidSeeiCloudLoginForStorageServices' => true,
'LastSeenCloudProductVersion' => node['platform_version'],
'LastPreLoginTasksPerformedVersion' => node['platform_version'],
'LastPreLoginTasksPerformedBuild' => node['platform_build'],
'LastPrivacyBundleVersion' => '2',
'LastSeenBuddyBuildVersion' => node['platform_build'],
'LastSeenDiagnosticsProductVersion' => node['platform_version'],
'LastSeenSiriProductVersion' => node['platform_version'],
'MiniBuddyLaunchReason' => 0,
}
end
def login_window_keypair_values
{ 'autoLoginUser' => new_resource.username,
'lastUser' => 'loggedIn',
'autoLoginUserScreenLocked' => false,
}
end
def sysadminctl
'/usr/sbin/sysadminctl'
end
def dscl
'/usr/bin/dscl'
end
def user_fullname
if new_resource.property_is_set?(:fullname)
['-fullName', new_resource.fullname]
else
''
end
end
def user_password
if new_resource.property_is_set?(:password)
['-password', new_resource.password]
else
''
end
end
def exec_sysadminctl(args)
shell_out!(sysadminctl, args).stderr
end
def logged_in?(user)
logged_in_user = shell_out('stat', '-f', '%Su', '/dev/console').stdout.chomp
logged_in_user == user
end
def validate_secure_token_modification
if !new_resource.property_is_set?(:existing_token_auth) || !new_resource.property_is_set?(:password)
raise "Both an existing_token_auth hash and the user password for #{new_resource.username} must be provided to modify secure token!" unless logged_in?('_mbsetupuser') || logged_in?('vagrant')
end
end
def token_credentials
if new_resource.property_is_set?(:existing_token_auth)
['-adminUser', new_resource.existing_token_auth[:username], '-adminPassword', new_resource.existing_token_auth[:password]]
else
''
end
end
def secure_token_enabled?
shell_out!(sysadminctl, '-secureTokenStatus', new_resource.username).stderr.include?('ENABLED')
end
def admin_user
if new_resource.property_is_set?(:admin)
'-admin'
else
''
end
end
def user_already_exists?
users_output = shell_out!('/usr/bin/dscl', '.', '-list', '/users').stdout
users_output.split("\n").include?(new_resource.username)
end
end
action :create do
if new_resource.secure_token && !secure_token_enabled? && !property_is_set?(:existing_token_auth)
raise "An existing_token_auth hash must be provided if you want a secure token for #{new_resource.username}!" unless logged_in?('_mbsetupuser') || logged_in?('vagrant')
end
unless property_is_set?(:password)
execute 'clear any existing password policies' do
command 'pwpolicy clearaccountpolicies'
not_if do
Plist.parse_xml(shell_out('pwpolicy getaccountpolicies').stdout)['policyCategoryPasswordContent']
.map { |policy| policy['policyContent'] }.include?("policyAttributePassword matches '.{0,}?'")
end
notifies :run, 'execute[enable passwords with zero length]', :immediately
end
execute 'enable passwords with zero length' do
command "pwpolicy setglobalpolicy 'minChars=0'"
action :nothing
end
end
unless ::File.exist?(user_home) && user_already_exists?
cmd = [*token_credentials, '-addUser', new_resource.username, *user_fullname, *user_password, admin_user]
output = exec_sysadminctl(cmd)
unless /creating user/.match?(output.downcase)
raise "error while creating user: #{output}"
end
end
if new_resource.secure_token && !secure_token_enabled?
validate_secure_token_modification
cmd = [*token_credentials, '-secureTokenOn', new_resource.username, *user_password]
output = exec_sysadminctl(cmd)
unless /done/.match?(output.downcase)
raise "error while modifying SecureToken: #{output}"
end
end
if !new_resource.secure_token && secure_token_enabled?
validate_secure_token_modification
cmd = [*token_credentials, '-secureTokenOff', new_resource.username, *user_password]
output = exec_sysadminctl(cmd)
unless /done/.match?(output.downcase)
raise "error while modifying SecureToken: #{output}"
end
end
if new_resource.hidden
execute "hide user #{new_resource.username}" do
key = 'IsHidden'
desired_value = '1'
read_command = shell_out(dscl, '.', 'read', user_home, key)
current_value = read_command.stdout.empty? ? 0 : read_command.stdout.split(':').last.strip
command [dscl, '.', 'create', user_home, key, desired_value]
not_if { current_value.eql? desired_value }
end
ruby_block "hide user #{new_resource.username} home directory #{user_home}" do
block do
FileUtils.mkdir_p user_hidden_home
FileUtils.cp_r(Dir[user_home.to_s], Dir[user_hidden_home.to_s])
end
end
execute 'update user record' do
command [dscl, '.', 'create', user_home, 'NFSHomeDirectory', user_hidden_home]
only_if { ::File.exist?(user_hidden_home) && ::File.exist?(user_home) }
end
execute 'remove Public Folder share point' do
command [dscl, '.', 'delete', user_sharepoints]
only_if { ::File.exist?(user_sharepoints) }
end
end
if new_resource.property_is_set?(:autologin)
setup_assistant_keypair_values.each do |e, v|
plist setup_assistant_plist do
entry e
value v
owner new_resource.username
group 'staff'
end
end
login_window_keypair_values.each do |e, v|
plist login_window_plist do
entry e
value v
end
end
file '/etc/kcpassword' do
content kcpassword_hash(new_resource.password)
owner 'root'
group 'wheel'
mode '0600'
sensitive true
end
end
if new_resource.property_is_set?(:groups)
if new_resource.groups.is_a? String
group new_resource.groups do
action :create
members new_resource.username
append true
end
else
new_resource.groups.each do |g|
group g do
action :create
members new_resource.username
append true
end
end
end
end
end
action :delete do
directory user_home do
recursive true
action :delete
only_if { ::File.exist? user_home }
end
if user_already_exists?
cmd = ['-deleteUser', new_resource.username]
output = exec_sysadminctl(cmd)
unless /deleting record|not found/.match?(output.downcase)
raise "error deleting user: #{output}"
end
end
end