mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #28465 from twangboy/fix_12363
Fix #12363: Password Expiration in Windows
This commit is contained in:
commit
f7042ba967
2 changed files with 228 additions and 15 deletions
|
@ -24,35 +24,91 @@ def info(name):
|
|||
Return information for the specified user
|
||||
This is just returns dummy data so that salt states can work.
|
||||
|
||||
:param str name: The name of the user account to show.
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' shadow.info root
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'passwd': '',
|
||||
'lstchg': '',
|
||||
'min': '',
|
||||
'max': '',
|
||||
'warn': '',
|
||||
'inact': '',
|
||||
'expire': ''}
|
||||
info = __salt__['user.info'](name=name)
|
||||
|
||||
ret = {'name': name,
|
||||
'passwd': '',
|
||||
'lstchg': '',
|
||||
'min': '',
|
||||
'max': '',
|
||||
'warn': '',
|
||||
'inact': '',
|
||||
'expire': ''}
|
||||
|
||||
if info:
|
||||
ret = {'name': info['name'],
|
||||
'passwd': 'Unavailable',
|
||||
'lstchg': info['password_changed'],
|
||||
'min': '',
|
||||
'max': '',
|
||||
'warn': '',
|
||||
'inact': '',
|
||||
'expire': info['expiration_date']}
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def set_expire(name, expire):
|
||||
'''
|
||||
Set the expiration date for a user account.
|
||||
|
||||
:param name: The name of the user account to edit.
|
||||
|
||||
:param expire: The date the account will expire.
|
||||
|
||||
:return: True if successful. False if unsuccessful.
|
||||
:rtype: bool
|
||||
'''
|
||||
return __salt__['user.update'](name, expiration_date=expire)
|
||||
|
||||
|
||||
def require_password_change(name):
|
||||
'''
|
||||
Require the user to change their password the next time they log in.
|
||||
|
||||
:param name: The name of the user account to require a password change.
|
||||
|
||||
:return: True if successful. False if unsuccessful.
|
||||
:rtype: bool
|
||||
'''
|
||||
return __salt__['user.update'](name, expired=True)
|
||||
|
||||
|
||||
def unlock_account(name):
|
||||
'''
|
||||
Unlocks a user account.
|
||||
|
||||
:param name: The name of the user account to unlock.
|
||||
|
||||
:return: True if successful. False if unsuccessful.
|
||||
:rtype: bool
|
||||
'''
|
||||
return __salt__['user.update'](name, unlock_account=True)
|
||||
|
||||
|
||||
def set_password(name, password):
|
||||
'''
|
||||
Set the password for a named user.
|
||||
|
||||
:param str name: The name of the user account
|
||||
|
||||
:param str password: The new password
|
||||
|
||||
:return: True if successful. False if unsuccessful.
|
||||
:rtype: bool
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' shadow.set_password root mysecretpassword
|
||||
'''
|
||||
cmd = ['net', 'user', name, password]
|
||||
ret = __salt__['cmd.run_all'](cmd, python_shell=False)
|
||||
|
||||
return not ret['retcode']
|
||||
return __salt__['user.update'](name=name, password=password)
|
||||
|
|
|
@ -15,6 +15,8 @@ Module for managing Windows Users
|
|||
This currently only works with local user accounts, not domain accounts
|
||||
'''
|
||||
from __future__ import absolute_import
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
try:
|
||||
from shlex import quote as _cmd_quote # pylint: disable=E0611
|
||||
|
@ -57,6 +59,59 @@ def __virtual__():
|
|||
return False
|
||||
|
||||
|
||||
def _get_date_time_format(dt_string):
|
||||
'''
|
||||
Copied from win_system.py (_get_date_time_format)
|
||||
|
||||
Function that detects the date/time format for the string passed.
|
||||
|
||||
:param str dt_string:
|
||||
A date/time string
|
||||
|
||||
:return: The format of the passed dt_string
|
||||
:rtype: str
|
||||
'''
|
||||
valid_formats = [
|
||||
'%Y-%m-%d %I:%M:%S %p',
|
||||
'%m-%d-%y %I:%M:%S %p',
|
||||
'%m-%d-%Y %I:%M:%S %p',
|
||||
'%m/%d/%y %I:%M:%S %p',
|
||||
'%m/%d/%Y %I:%M:%S %p',
|
||||
'%Y/%m/%d %I:%M:%S %p',
|
||||
'%Y-%m-%d %I:%M:%S',
|
||||
'%m-%d-%y %I:%M:%S',
|
||||
'%m-%d-%Y %I:%M:%S',
|
||||
'%m/%d/%y %I:%M:%S',
|
||||
'%m/%d/%Y %I:%M:%S',
|
||||
'%Y/%m/%d %I:%M:%S',
|
||||
'%Y-%m-%d %I:%M %p',
|
||||
'%m-%d-%y %I:%M %p',
|
||||
'%m-%d-%Y %I:%M %p',
|
||||
'%m/%d/%y %I:%M %p',
|
||||
'%m/%d/%Y %I:%M %p',
|
||||
'%Y/%m/%d %I:%M %p',
|
||||
'%Y-%m-%d %I:%M',
|
||||
'%m-%d-%y %I:%M',
|
||||
'%m-%d-%Y %I:%M',
|
||||
'%m/%d/%y %I:%M',
|
||||
'%m/%d/%Y %I:%M',
|
||||
'%Y/%m/%d %I:%M',
|
||||
'%Y-%m-%d',
|
||||
'%m-%d-%y',
|
||||
'%m-%d-%Y',
|
||||
'%m/%d/%y',
|
||||
'%m/%d/%Y',
|
||||
'%Y/%m/%d',
|
||||
]
|
||||
for dt_format in valid_formats:
|
||||
try:
|
||||
datetime.strptime(dt_string, dt_format)
|
||||
return dt_format
|
||||
except ValueError:
|
||||
continue
|
||||
return False
|
||||
|
||||
|
||||
def add(name,
|
||||
password=None,
|
||||
fullname=False,
|
||||
|
@ -147,7 +202,13 @@ def update(name,
|
|||
home=None,
|
||||
homedrive=None,
|
||||
logonscript=None,
|
||||
profile=None):
|
||||
profile=None,
|
||||
expiration_date=None,
|
||||
expired=None,
|
||||
account_disabled=None,
|
||||
unlock_account=None,
|
||||
password_never_expires=None,
|
||||
disallow_change_password=None):
|
||||
r'''
|
||||
Updates settings for the windows user. Name is the only required parameter.
|
||||
Settings will only be changed if the parameter is passed a value.
|
||||
|
@ -179,6 +240,27 @@ def update(name,
|
|||
:param str profile:
|
||||
The path to the user's profile directory.
|
||||
|
||||
:param date expiration_date: The date and time when the account expires. Can
|
||||
be a valid date/time string. To set to never expire pass the string 'Never'.
|
||||
|
||||
:param bool expired: Pass `True` to expire the account. The user will be
|
||||
prompted to change their password at the next logon. Pass `False` to mark
|
||||
the account as 'not expired'. You can't use this to negate the expiration if
|
||||
the expiration was caused by the account expiring. You'll have to change
|
||||
the `expiration_date` as well.
|
||||
|
||||
:param bool account_disabled: True disables the account. False enables the
|
||||
account.
|
||||
|
||||
:param bool unlock_account: True unlocks a locked user account. False is
|
||||
ignored.
|
||||
|
||||
:param bool password_never_expires: True sets the password to never expire.
|
||||
False allows the password to expire.
|
||||
|
||||
:param bool disallow_change_password: True blocks the user from changing
|
||||
the password. False allows the user to change the password.
|
||||
|
||||
:return:
|
||||
True if successful. False is unsuccessful.
|
||||
:rtype: bool
|
||||
|
@ -219,6 +301,39 @@ def update(name,
|
|||
user_info['full_name'] = fullname
|
||||
if profile:
|
||||
user_info['profile'] = profile
|
||||
if expiration_date:
|
||||
if expiration_date == 'Never':
|
||||
user_info['acct_expires'] = win32netcon.TIMEQ_FOREVER
|
||||
else:
|
||||
date_format = _get_date_time_format(expiration_date)
|
||||
if date_format:
|
||||
dt_obj = datetime.strptime(expiration_date, date_format)
|
||||
else:
|
||||
return 'Invalid start_date'
|
||||
user_info['acct_expires'] = time.mktime(dt_obj.timetuple())
|
||||
if expired is not None:
|
||||
if expired:
|
||||
user_info['password_expired'] = 1
|
||||
else:
|
||||
user_info['password_expired'] = 0
|
||||
if account_disabled is not None:
|
||||
if account_disabled:
|
||||
user_info['flags'] |= win32netcon.UF_ACCOUNTDISABLE
|
||||
else:
|
||||
user_info['flags'] ^= win32netcon.UF_ACCOUNTDISABLE
|
||||
if unlock_account is not None:
|
||||
if unlock_account:
|
||||
user_info['flags'] ^= win32netcon.UF_LOCKOUT
|
||||
if password_never_expires is not None:
|
||||
if password_never_expires:
|
||||
user_info['flags'] |= win32netcon.UF_DONT_EXPIRE_PASSWD
|
||||
else:
|
||||
user_info['flags'] ^= win32netcon.UF_DONT_EXPIRE_PASSWD
|
||||
if disallow_change_password is not None:
|
||||
if disallow_change_password:
|
||||
user_info['flags'] |= win32netcon.UF_PASSWD_CANT_CHANGE
|
||||
else:
|
||||
user_info['flags'] ^= win32netcon.UF_PASSWD_CANT_CHANGE
|
||||
|
||||
# Apply new settings
|
||||
try:
|
||||
|
@ -626,6 +741,14 @@ def info(name):
|
|||
- home
|
||||
- homedrive
|
||||
- groups
|
||||
- password_changed
|
||||
- successful_logon_attempts
|
||||
- failed_logon_attempts
|
||||
- last_logon
|
||||
- account_disabled
|
||||
- account_locked
|
||||
- password_never_expires
|
||||
- disallow_change_password
|
||||
- gid
|
||||
:rtype: dict
|
||||
|
||||
|
@ -658,6 +781,19 @@ def info(name):
|
|||
ret['active'] = (not bool(items['flags'] & win32netcon.UF_ACCOUNTDISABLE))
|
||||
ret['logonscript'] = items['script_path']
|
||||
ret['profile'] = items['profile']
|
||||
ret['failed_logon_attempts'] = items['bad_pw_count']
|
||||
ret['successful_logon_attempts'] = items['num_logons']
|
||||
secs = time.mktime(datetime.now().timetuple()) - items['password_age']
|
||||
ret['password_changed'] = datetime.fromtimestamp(secs). \
|
||||
strftime('%Y-%m-%d %H:%M:%S')
|
||||
if items['last_logon'] == 0:
|
||||
ret['last_logon'] = 'Never'
|
||||
else:
|
||||
ret['last_logon'] = datetime.fromtimestamp(items['last_logon']).\
|
||||
strftime('%Y-%m-%d %H:%M:%S')
|
||||
ret['expiration_date'] = datetime.fromtimestamp(items['acct_expires']).\
|
||||
strftime('%Y-%m-%d %H:%M:%S')
|
||||
ret['expired'] = items['password_expired'] == 1
|
||||
if not ret['profile']:
|
||||
ret['profile'] = _get_userprofile_from_registry(name, ret['uid'])
|
||||
ret['home'] = items['home_dir']
|
||||
|
@ -665,9 +801,30 @@ def info(name):
|
|||
if not ret['home']:
|
||||
ret['home'] = ret['profile']
|
||||
ret['groups'] = groups
|
||||
if items['flags'] & win32netcon.UF_DONT_EXPIRE_PASSWD == 0:
|
||||
ret['password_never_expires'] = False
|
||||
else:
|
||||
ret['password_never_expires'] = True
|
||||
if items['flags'] & win32netcon.UF_ACCOUNTDISABLE == 0:
|
||||
ret['account_disabled'] = False
|
||||
else:
|
||||
ret['account_disabled'] = True
|
||||
if items['flags'] & win32netcon.UF_LOCKOUT == 0:
|
||||
ret['account_locked'] = False
|
||||
else:
|
||||
ret['account_locked'] = True
|
||||
if items['flags'] & win32netcon.UF_PASSWD_CANT_CHANGE == 0:
|
||||
ret['disallow_change_password'] = False
|
||||
else:
|
||||
ret['disallow_change_password'] = True
|
||||
|
||||
ret['gid'] = ''
|
||||
|
||||
return ret
|
||||
return ret
|
||||
|
||||
else:
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _get_userprofile_from_registry(user, sid):
|
||||
|
|
Loading…
Add table
Reference in a new issue