mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 01:30:20 +00:00
fixes saltstack/salt#62856 add password/account locking/unlocking in user.present state on supported operating systems
This commit is contained in:
parent
8f27d32a1f
commit
4e1bbec71d
3 changed files with 176 additions and 0 deletions
1
changelog/62856.added
Normal file
1
changelog/62856.added
Normal file
|
@ -0,0 +1 @@
|
|||
Add password/account locking/unlocking in user.present state on supported operating systems
|
|
@ -78,6 +78,7 @@ def _changes(
|
|||
win_description=None,
|
||||
allow_uid_change=False,
|
||||
allow_gid_change=False,
|
||||
password_lock=None,
|
||||
):
|
||||
"""
|
||||
Return a dict of the changes required for a user if the user is present,
|
||||
|
@ -158,6 +159,10 @@ def _changes(
|
|||
change["warndays"] = warndays
|
||||
if expire and lshad["expire"] != expire:
|
||||
change["expire"] = expire
|
||||
if (password_lock and not lshad["passwd"].startswith("!")) or (
|
||||
password_lock is False and lshad["passwd"].startswith("!")
|
||||
):
|
||||
change["password_lock"] = password_lock
|
||||
elif "shadow.info" in __salt__ and salt.utils.platform.is_windows():
|
||||
if (
|
||||
expire
|
||||
|
@ -166,6 +171,8 @@ def _changes(
|
|||
!= salt.utils.dateutils.strftime(expire)
|
||||
):
|
||||
change["expire"] = expire
|
||||
if password_lock is False and lusr["account_locked"]:
|
||||
change["password_lock"] = password_lock
|
||||
|
||||
# GECOS fields
|
||||
fullname = salt.utils.data.decode(fullname)
|
||||
|
@ -266,6 +273,7 @@ def present(
|
|||
nologinit=False,
|
||||
allow_uid_change=False,
|
||||
allow_gid_change=False,
|
||||
password_lock=None,
|
||||
):
|
||||
"""
|
||||
Ensure that the named user is present with the specified properties
|
||||
|
@ -368,6 +376,14 @@ def present(
|
|||
empty_password
|
||||
Set to True to enable password-less login for user, Default is ``False``.
|
||||
|
||||
password_lock
|
||||
Set to ``False`` to unlock a user's password (or Windows account). On
|
||||
non-Windows systems ONLY, this parameter can be set to ``True`` to lock
|
||||
a user's password. Default is ``None``, which does not take action on
|
||||
the password (or Windows account).
|
||||
|
||||
.. versionadded:: 3006.0
|
||||
|
||||
shell
|
||||
The login shell, defaults to the system default shell
|
||||
|
||||
|
@ -597,6 +613,7 @@ def present(
|
|||
win_description,
|
||||
allow_uid_change,
|
||||
allow_gid_change,
|
||||
password_lock=password_lock,
|
||||
)
|
||||
except CommandExecutionError as exc:
|
||||
ret["result"] = False
|
||||
|
@ -633,6 +650,17 @@ def present(
|
|||
if changes.pop("empty_password", False) is True:
|
||||
__salt__["shadow.del_password"](name)
|
||||
|
||||
if "password_lock" in changes:
|
||||
passlock = changes.pop("password_lock")
|
||||
if not passlock and salt.utils.platform.is_windows():
|
||||
__salt__["shadow.unlock_account"](name)
|
||||
elif not passlock:
|
||||
__salt__["shadow.unlock_password"](name)
|
||||
elif passlock and not salt.utils.platform.is_windows():
|
||||
__salt__["shadow.lock_password"](name)
|
||||
else:
|
||||
log.warning("Account locking is not available on Windows.")
|
||||
|
||||
if "date" in changes:
|
||||
del changes["date"]
|
||||
__salt__["shadow.set_date"](name, date)
|
||||
|
@ -766,6 +794,7 @@ def present(
|
|||
win_description,
|
||||
allow_uid_change=True,
|
||||
allow_gid_change=True,
|
||||
password_lock=password_lock,
|
||||
)
|
||||
# allow_uid_change and allow_gid_change passed as True to avoid race
|
||||
# conditions where a uid/gid is modified outside of Salt. If an
|
||||
|
|
|
@ -7,6 +7,7 @@ import logging
|
|||
import pytest
|
||||
|
||||
import salt.states.user as user
|
||||
import salt.utils.platform
|
||||
from tests.support.mock import MagicMock, Mock, patch
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -313,3 +314,148 @@ def test_gecos_field_changes_in_user_present():
|
|||
):
|
||||
res = user.present("Foo", homephone=44566, fullname="Bar Bar")
|
||||
assert res["changes"] == {"homephone": "44566", "fullname": "Bar Bar"}
|
||||
|
||||
|
||||
def test_present_password_lock_test_mode():
|
||||
ret = {
|
||||
"name": "salt",
|
||||
"changes": {},
|
||||
"result": True,
|
||||
"comment": "User salt is present and up to date",
|
||||
}
|
||||
mock_info = MagicMock(
|
||||
return_value={
|
||||
"uid": 5000,
|
||||
"gid": 5000,
|
||||
"groups": [],
|
||||
"home": "/home/salt",
|
||||
"fullname": "Salty McSalterson",
|
||||
}
|
||||
)
|
||||
shadow_info = MagicMock(
|
||||
side_effect=[
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": "!"},
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": ""},
|
||||
]
|
||||
)
|
||||
shadow_hash = MagicMock(return_value="abcd")
|
||||
|
||||
with patch.dict(user.__grains__, {"kernel": "Linux"}), patch.dict(
|
||||
user.__salt__,
|
||||
{
|
||||
"shadow.default_hash": shadow_hash,
|
||||
"shadow.info": shadow_info,
|
||||
"user.info": mock_info,
|
||||
"file.gid_to_group": MagicMock(return_value=5000),
|
||||
},
|
||||
), patch.dict(user.__opts__, {"test": True}):
|
||||
assert user.present("salt", createhome=False, password_lock=True) == ret
|
||||
ret.update(
|
||||
{
|
||||
"comment": "The following user attributes are set to be changed:\npassword_lock: True\n"
|
||||
}
|
||||
)
|
||||
ret.update({"result": None})
|
||||
assert user.present("salt", createhome=False, password_lock=True) == ret
|
||||
|
||||
|
||||
def test_present_password_lock():
|
||||
ret = {
|
||||
"name": "salt",
|
||||
"changes": {"passwd": "XXX-REDACTED-XXX"},
|
||||
"result": True,
|
||||
"comment": "Updated user salt",
|
||||
}
|
||||
mock_info = MagicMock(
|
||||
return_value={
|
||||
"uid": 5000,
|
||||
"gid": 5000,
|
||||
"groups": [],
|
||||
"home": "/home/salt",
|
||||
"fullname": "Salty McSalterson",
|
||||
}
|
||||
)
|
||||
shadow_info = MagicMock(
|
||||
side_effect=[
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": ""},
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": ""},
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": "!"},
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": "!"},
|
||||
]
|
||||
)
|
||||
shadow_hash = MagicMock(return_value="abcd")
|
||||
|
||||
unlock_account = MagicMock()
|
||||
unlock_password = MagicMock()
|
||||
lock_password = MagicMock()
|
||||
|
||||
with patch.dict(user.__grains__, {"kernel": "Linux"}), patch.dict(
|
||||
user.__salt__,
|
||||
{
|
||||
"shadow.default_hash": shadow_hash,
|
||||
"shadow.info": shadow_info,
|
||||
"user.info": mock_info,
|
||||
"file.gid_to_group": MagicMock(return_value=5000),
|
||||
"shadow.unlock_account": unlock_account,
|
||||
"shadow.unlock_password": unlock_password,
|
||||
"shadow.lock_password": lock_password,
|
||||
},
|
||||
), patch.dict(user.__opts__, {"test": False}):
|
||||
assert user.present("salt", createhome=False, password_lock=True) == ret
|
||||
unlock_password.assert_not_called()
|
||||
unlock_account.assert_not_called()
|
||||
if salt.utils.platform.is_windows():
|
||||
lock_password.assert_not_called()
|
||||
else:
|
||||
lock_password.assert_called_once()
|
||||
|
||||
|
||||
def test_present_password_unlock():
|
||||
ret = {
|
||||
"name": "salt",
|
||||
"changes": {"passwd": "XXX-REDACTED-XXX"},
|
||||
"result": True,
|
||||
"comment": "Updated user salt",
|
||||
}
|
||||
mock_info = MagicMock(
|
||||
return_value={
|
||||
"uid": 5000,
|
||||
"gid": 5000,
|
||||
"groups": [],
|
||||
"home": "/home/salt",
|
||||
"fullname": "Salty McSalterson",
|
||||
}
|
||||
)
|
||||
shadow_info = MagicMock(
|
||||
side_effect=[
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": "!"},
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": "!"},
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": ""},
|
||||
{"min": 2, "max": 88888, "inact": 77, "warn": 14, "passwd": ""},
|
||||
]
|
||||
)
|
||||
shadow_hash = MagicMock(return_value="abcd")
|
||||
|
||||
unlock_account = MagicMock()
|
||||
unlock_password = MagicMock()
|
||||
lock_password = MagicMock()
|
||||
with patch.dict(user.__grains__, {"kernel": "Linux"}), patch.dict(
|
||||
user.__salt__,
|
||||
{
|
||||
"shadow.default_hash": shadow_hash,
|
||||
"shadow.info": shadow_info,
|
||||
"user.info": mock_info,
|
||||
"file.gid_to_group": MagicMock(return_value=5000),
|
||||
"shadow.unlock_account": unlock_account,
|
||||
"shadow.unlock_password": unlock_password,
|
||||
"shadow.lock_password": lock_password,
|
||||
},
|
||||
), patch.dict(user.__opts__, {"test": False}):
|
||||
assert user.present("salt", createhome=False, password_lock=False) == ret
|
||||
lock_password.assert_not_called()
|
||||
if salt.utils.platform.is_windows():
|
||||
unlock_account.assert_called_once()
|
||||
unlock_password.assert_not_called()
|
||||
else:
|
||||
unlock_password.assert_called_once()
|
||||
unlock_account.assert_not_called()
|
||||
|
|
Loading…
Add table
Reference in a new issue