Merge pull request #44748 from twangboy/osx_fix_auto_login

Fix auto login support for OSX
This commit is contained in:
Nicole Thomas 2017-12-07 09:22:22 -05:00 committed by GitHub
commit 74ee7ce541
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 159 additions and 7 deletions

View file

@ -19,11 +19,12 @@ import logging
import time
# Import 3rdp-party libs
from salt.ext.six.moves import range # pylint: disable=import-error,redefined-builtin
from salt.ext.six.moves import range, map # pylint: disable=import-error,redefined-builtin
from salt.ext.six import string_types
# Import salt libs
import salt.utils
import salt.utils.files
import salt.utils.decorators as decorators
from salt.utils.locales import sdecode as _sdecode
from salt.exceptions import CommandExecutionError, SaltInvocationError
@ -520,16 +521,72 @@ def get_auto_login():
return False if ret['retcode'] else ret['stdout']
def enable_auto_login(name):
def _kcpassword(password):
'''
Internal function for obfuscating the password used for AutoLogin
This is later written as the contents of the ``/etc/kcpassword`` file
.. versionadded:: 2017.7.3
Adapted from:
https://github.com/timsutton/osx-vm-templates/blob/master/scripts/support/set_kcpassword.py
Args:
password(str):
The password to obfuscate
Returns:
str: The obfuscated password
'''
# The magic 11 bytes - these are just repeated
# 0x7D 0x89 0x52 0x23 0xD2 0xBC 0xDD 0xEA 0xA3 0xB9 0x1F
key = [125, 137, 82, 35, 210, 188, 221, 234, 163, 185, 31]
key_len = len(key)
# Convert each character to a byte
password = list(map(ord, password))
# pad password length out to an even multiple of key length
remainder = len(password) % key_len
if remainder > 0:
password = password + [0] * (key_len - remainder)
# Break the password into chunks the size of len(key) (11)
for chunk_index in range(0, len(password), len(key)):
# Reset the key_index to 0 for each iteration
key_index = 0
# Do an XOR on each character of that chunk of the password with the
# corresponding item in the key
# The length of the password, or the length of the key, whichever is
# smaller
for password_index in range(chunk_index,
min(chunk_index + len(key), len(password))):
password[password_index] = password[password_index] ^ key[key_index]
key_index += 1
# Convert each byte back to a character
password = list(map(chr, password))
return ''.join(password)
def enable_auto_login(name, password):
'''
.. versionadded:: 2016.3.0
Configures the machine to auto login with the specified user
:param str name: The user account use for auto login
Args:
:return: True if successful, False if not
:rtype: bool
name (str): The user account use for auto login
password (str): The password to user for auto login
.. versionadded:: 2017.7.3
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -537,6 +594,7 @@ def enable_auto_login(name):
salt '*' user.enable_auto_login stevej
'''
# Make the entry into the defaults file
cmd = ['defaults',
'write',
'/Library/Preferences/com.apple.loginwindow.plist',
@ -544,6 +602,13 @@ def enable_auto_login(name):
name]
__salt__['cmd.run'](cmd)
current = get_auto_login()
# Create/Update the kcpassword file with an obfuscated password
o_password = _kcpassword(password=password)
with salt.utils.files.set_umask(0o077):
with salt.utils.fopen('/etc/kcpassword', 'w') as fd:
fd.write(o_password)
return current if isinstance(current, bool) else current.lower() == name.lower()
@ -553,8 +618,8 @@ def disable_auto_login():
Disables auto login on the machine
:return: True if successful, False if not
:rtype: bool
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -562,6 +627,11 @@ def disable_auto_login():
salt '*' user.disable_auto_login
'''
# Remove the kcpassword file
cmd = 'rm -f /etc/kcpassword'
__salt__['cmd.run'](cmd)
# Remove the entry from the defaults file
cmd = ['defaults',
'delete',
'/Library/Preferences/com.apple.loginwindow.plist',

View file

@ -7,12 +7,14 @@
from __future__ import absolute_import
import random
import string
import os
# Import Salt Testing Libs
from tests.support.case import ModuleCase
from tests.support.helpers import destructiveTest, skip_if_not_root
# Import Salt Libs
import salt.utils
from salt.exceptions import CommandExecutionError
# Import 3rd-party libs
@ -148,6 +150,86 @@ class MacUserModuleTest(ModuleCase):
self.run_function('user.delete', [CHANGE_USER])
raise
def test_mac_user_enable_auto_login(self):
'''
Tests mac_user functions that enable auto login
'''
# Make sure auto login is disabled before we start
if self.run_function('user.get_auto_login'):
self.skipTest('Auto login already enabled')
try:
# Does enable return True
self.assertTrue(
self.run_function('user.enable_auto_login',
['Spongebob', 'Squarepants']))
# Did it set the user entry in the plist file
self.assertEqual(
self.run_function('user.get_auto_login'),
'Spongebob')
# Did it generate the `/etc/kcpassword` file
self.assertTrue(os.path.exists('/etc/kcpassword'))
# Are the contents of the file correct
test_data = b'.\xf8\'B\xa0\xd9\xad\x8b\xcd\xcdl'
with salt.utils.fopen('/etc/kcpassword', 'rb') as f:
file_data = f.read()
self.assertEqual(test_data, file_data)
# Does disable return True
self.assertTrue(self.run_function('user.disable_auto_login'))
# Does it remove the user entry in the plist file
self.assertFalse(self.run_function('user.get_auto_login'))
# Is the `/etc/kcpassword` file removed
self.assertFalse(os.path.exists('/etc/kcpassword'))
finally:
# Make sure auto_login is disabled
self.assertTrue(self.run_function('user.disable_auto_login'))
# Make sure autologin is disabled
if self.run_function('user.get_auto_login'):
raise Exception('Failed to disable auto login')
def test_mac_user_disable_auto_login(self):
'''
Tests mac_user functions that disable auto login
'''
# Make sure auto login is enabled before we start
# Is there an existing setting
if self.run_function('user.get_auto_login'):
self.skipTest('Auto login already enabled')
try:
# Enable auto login for the test
self.run_function('user.enable_auto_login',
['Spongebob', 'Squarepants'])
# Make sure auto login got set up
if not self.run_function('user.get_auto_login') == 'Spongebob':
raise Exception('Failed to enable auto login')
# Does disable return True
self.assertTrue(self.run_function('user.disable_auto_login'))
# Does it remove the user entry in the plist file
self.assertFalse(self.run_function('user.get_auto_login'))
# Is the `/etc/kcpassword` file removed
self.assertFalse(os.path.exists('/etc/kcpassword'))
finally:
# Make sure auto login is disabled
self.assertTrue(self.run_function('user.disable_auto_login'))
# Make sure auto login is disabled
if self.run_function('user.get_auto_login'):
raise Exception('Failed to disable auto login')
def tearDown(self):
'''
Clean up after tests