mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 01:30:20 +00:00
512 lines
14 KiB
Python
512 lines
14 KiB
Python
"""
|
|
tests for user state
|
|
user absent
|
|
user present
|
|
user present with custom homedir
|
|
"""
|
|
|
|
import pathlib
|
|
import random
|
|
import shutil
|
|
import sys
|
|
|
|
import pytest
|
|
from saltfactories.utils import random_string
|
|
|
|
import salt.utils.files
|
|
import salt.utils.platform
|
|
|
|
try:
|
|
import grp
|
|
except ImportError:
|
|
grp = None
|
|
|
|
pytestmark = [
|
|
pytest.mark.slow_test,
|
|
pytest.mark.skip_if_not_root,
|
|
pytest.mark.destructive_test,
|
|
pytest.mark.windows_whitelisted,
|
|
]
|
|
|
|
ANSI_FILESYSTEM_ENCODING = sys.getfilesystemencoding().startswith("ANSI")
|
|
|
|
|
|
@pytest.fixture
|
|
def username(sminion):
|
|
_username = random_string("new-account-", uppercase=False)
|
|
try:
|
|
yield _username
|
|
finally:
|
|
try:
|
|
sminion.functions.user.delete(_username, remove=True, force=True)
|
|
except Exception: # pylint: disable=broad-except
|
|
# The point here is just system cleanup. It can fail if no account was created
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def guid():
|
|
return random.randint(60000, 61000)
|
|
|
|
|
|
@pytest.fixture
|
|
def user_home(username, tmp_path):
|
|
if salt.utils.platform.is_windows():
|
|
return tmp_path / username
|
|
|
|
return pathlib.Path("/var/lib") / username
|
|
|
|
|
|
@pytest.fixture
|
|
def group_1(username, grains):
|
|
groupname = username
|
|
if salt.utils.platform.is_darwin():
|
|
groupname = "staff"
|
|
elif salt.utils.platform.is_photonos():
|
|
groupname = "users"
|
|
elif grains["os_family"] in ("Suse",):
|
|
groupname = "users"
|
|
with pytest.helpers.create_group(name=groupname) as group:
|
|
yield group
|
|
|
|
|
|
@pytest.fixture
|
|
def group_2():
|
|
with pytest.helpers.create_group() as group:
|
|
yield group
|
|
|
|
|
|
@pytest.fixture
|
|
def existing_account():
|
|
with pytest.helpers.create_account(create_group=True) as _account:
|
|
yield _account
|
|
|
|
|
|
@pytest.mark.slow_test
|
|
def test_user_absent(states):
|
|
"""
|
|
Test user.absent with a non existing account
|
|
"""
|
|
ret = states.user.absent(name=random_string("account-", uppercase=False))
|
|
assert ret.result is True
|
|
|
|
|
|
def test_user_absent_existing_account(states, existing_account):
|
|
"""
|
|
Test user.absent with an existing account
|
|
"""
|
|
ret = states.user.absent(name=existing_account.username)
|
|
assert ret.result is True
|
|
|
|
|
|
def test_user_present(states, username):
|
|
"""
|
|
Test user.present with a non existing account
|
|
"""
|
|
ret = states.user.present(name=username)
|
|
assert ret.result is True
|
|
|
|
|
|
def test_user_present_with_existing_group(states, username, existing_account):
|
|
ret = states.user.present(username, gid=existing_account.group.info.gid)
|
|
assert ret.result is True
|
|
|
|
|
|
@pytest.mark.skip_on_windows(
|
|
reason="Home directories are handled differently in Windows"
|
|
)
|
|
def test_user_present_when_home_dir_does_not_18843(states, existing_account):
|
|
"""
|
|
User exists but home directory does not. Home directory get's created
|
|
"""
|
|
shutil.rmtree(existing_account.info.home)
|
|
ret = states.user.present(
|
|
name=existing_account.username,
|
|
home=existing_account.info.home,
|
|
)
|
|
assert ret.result is True
|
|
assert pathlib.Path(existing_account.info.home).is_dir()
|
|
|
|
|
|
def test_user_present_nondefault(grains, modules, states, username, user_home):
|
|
ret = states.user.present(name=username, home=str(user_home))
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
|
|
if salt.utils.platform.is_windows():
|
|
group_name = modules.user.list_groups(username)
|
|
else:
|
|
group_name = grp.getgrgid(user_info["gid"]).gr_name
|
|
|
|
if not salt.utils.platform.is_darwin() and not salt.utils.platform.is_windows():
|
|
assert user_home.is_dir()
|
|
|
|
if grains["os_family"] in ("Suse",):
|
|
expected_group_name = "users"
|
|
elif grains["os_family"] == "MacOS":
|
|
expected_group_name = "staff"
|
|
elif salt.utils.platform.is_windows():
|
|
expected_group_name = []
|
|
elif grains["os"] == "VMware Photon OS":
|
|
expected_group_name = "users"
|
|
else:
|
|
expected_group_name = username
|
|
assert group_name == expected_group_name
|
|
|
|
|
|
@pytest.mark.skip_on_windows(reason="windows minion does not support 'usergroup'")
|
|
def test_user_present_usergroup_false(modules, states, username, group_1, user_home):
|
|
ret = states.user.present(
|
|
name=username,
|
|
gid=group_1.info.gid,
|
|
usergroup=False,
|
|
home=str(user_home),
|
|
)
|
|
assert ret.result is True
|
|
|
|
if not salt.utils.platform.is_darwin():
|
|
assert user_home.is_dir()
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
|
|
group_name = grp.getgrgid(user_info["gid"]).gr_name
|
|
assert group_name == group_1.name
|
|
|
|
|
|
@pytest.mark.skip_on_windows(reason="windows minion does not support 'usergroup'")
|
|
def test_user_present_usergroup_true(modules, states, username, user_home, group_1):
|
|
ret = states.user.present(
|
|
name=username,
|
|
gid=group_1.info.gid,
|
|
usergroup=True,
|
|
home=str(user_home),
|
|
)
|
|
assert ret.result is True
|
|
|
|
if not salt.utils.platform.is_darwin():
|
|
assert user_home.is_dir()
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
|
|
group_name = grp.getgrgid(user_info["gid"]).gr_name
|
|
assert group_name == group_1.name
|
|
|
|
|
|
def _check_skip(grains):
|
|
if grains["os"] == "MacOS":
|
|
return True
|
|
return False
|
|
|
|
|
|
@pytest.mark.skipif(
|
|
ANSI_FILESYSTEM_ENCODING,
|
|
reason=(
|
|
"A system encoding which supports Unicode characters must be set. "
|
|
f"Current setting is: {sys.getfilesystemencoding()}. "
|
|
"Try setting $LANG='en_US.UTF-8'"
|
|
),
|
|
)
|
|
@pytest.mark.skip_initial_gh_actions_failure(skip=_check_skip)
|
|
def test_user_present_unicode(states, username, subtests):
|
|
"""
|
|
It ensures that unicode GECOS data will be properly handled, without
|
|
any encoding-related failures.
|
|
"""
|
|
with subtests.test("Non existing account"):
|
|
ret = states.user.present(
|
|
name=username,
|
|
fullname="Sålt Test",
|
|
roomnumber="①②③",
|
|
workphone="١٢٣٤",
|
|
homephone="६७८",
|
|
)
|
|
assert ret.result is True
|
|
|
|
with subtests.test("Update existing account"):
|
|
ret = states.user.present(
|
|
name=username,
|
|
fullname="Sålt Test",
|
|
roomnumber="①②③",
|
|
workphone="١٢٣٤",
|
|
homephone="६७८",
|
|
)
|
|
assert ret.result is True
|
|
|
|
|
|
@pytest.mark.skip_on_windows(
|
|
reason="windows minion does not support roomnumber or phone",
|
|
)
|
|
def test_user_present_gecos(modules, states, username):
|
|
"""
|
|
It ensures that numeric GECOS data will be properly coerced to strings,
|
|
otherwise the state will fail because the GECOS fields are written as
|
|
strings (and show up in the user.info output as such). Thus the
|
|
comparison will fail, since '12345' != 12345.
|
|
"""
|
|
fullname = 123345
|
|
roomnumber = 123
|
|
workphone = homephone = 1234567890
|
|
ret = states.user.present(
|
|
name=username,
|
|
fullname=fullname,
|
|
roomnumber=roomnumber,
|
|
workphone=workphone,
|
|
homephone=homephone,
|
|
)
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
|
|
assert user_info["fullname"] == str(fullname)
|
|
if not salt.utils.platform.is_darwin():
|
|
# MacOS does not supply the following GECOS fields
|
|
assert user_info["roomnumber"] == str(roomnumber)
|
|
assert user_info["workphone"] == str(workphone)
|
|
assert user_info["homephone"] == str(homephone)
|
|
|
|
|
|
@pytest.mark.skip_on_windows(
|
|
reason="windows minion does not support roomnumber or phone",
|
|
)
|
|
def test_user_present_gecos_empty_fields(modules, states, username):
|
|
"""
|
|
It ensures that if no GECOS data is supplied, the fields will be coerced
|
|
into empty strings as opposed to the string "None".
|
|
"""
|
|
fullname = roomnumber = workphone = homephone = ""
|
|
ret = states.user.present(
|
|
name=username,
|
|
fullname=fullname,
|
|
roomnumber=roomnumber,
|
|
workphone=workphone,
|
|
homephone=homephone,
|
|
)
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
|
|
assert user_info["fullname"] == fullname
|
|
if not salt.utils.platform.is_darwin():
|
|
# MacOS does not supply the following GECOS fields
|
|
assert user_info["roomnumber"] == roomnumber
|
|
assert user_info["workphone"] == workphone
|
|
assert user_info["homephone"] == homephone
|
|
|
|
|
|
@pytest.mark.skip_on_windows(reason="windows minion does not support createhome")
|
|
@pytest.mark.parametrize("createhome", [True, False])
|
|
def test_user_present_home_directory_created(modules, states, username, createhome):
|
|
"""
|
|
It ensures that the home directory is created.
|
|
"""
|
|
ret = states.user.present(name=username, createhome=createhome)
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
|
|
assert pathlib.Path(user_info["home"]).is_dir() is createhome
|
|
|
|
|
|
@pytest.mark.skip_on_darwin(reason="groups/gid not fully supported")
|
|
@pytest.mark.skip_on_windows(reason="groups/gid not fully supported")
|
|
def test_user_present_change_gid_but_keep_group(
|
|
modules, states, username, group_1, group_2
|
|
):
|
|
"""
|
|
This tests the case in which the default group is changed at the same
|
|
time as it is also moved into the "groups" list.
|
|
"""
|
|
|
|
# Add the user
|
|
ret = states.user.present(name=username, gid=group_1.info.gid)
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
|
|
assert user_info["gid"] == group_1.info.gid
|
|
assert user_info["groups"] == [group_1.name]
|
|
|
|
# Now change the gid and move alt_group to the groups list in the
|
|
# same salt run.
|
|
ret = states.user.present(
|
|
name=username,
|
|
gid=group_2.info.gid,
|
|
groups=[group_1.name],
|
|
allow_gid_change=True,
|
|
)
|
|
assert ret.result is True
|
|
|
|
# Be sure that we did what we intended
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
|
|
assert user_info["gid"] == group_2.info.gid
|
|
assert user_info["groups"] == [group_2.name, group_1.name]
|
|
|
|
|
|
@pytest.mark.skip_unless_on_windows
|
|
def test_user_present_existing(states, username):
|
|
win_profile = f"C:\\User\\{username}"
|
|
win_logonscript = "C:\\logon.vbs"
|
|
win_description = "Test User Account"
|
|
ret = states.user.present(
|
|
name=username,
|
|
win_homedrive="U:",
|
|
win_profile=win_profile,
|
|
win_logonscript=win_logonscript,
|
|
win_description=win_description,
|
|
)
|
|
assert ret.result is True
|
|
|
|
win_profile = f"C:\\Users\\{username}"
|
|
win_description = "Temporary Account"
|
|
ret = states.user.present(
|
|
name=username,
|
|
win_homedrive="R:",
|
|
win_profile=win_profile,
|
|
win_logonscript=win_logonscript,
|
|
win_description=win_description,
|
|
)
|
|
assert ret.result is True
|
|
assert ret.changes
|
|
assert "homedrive" in ret.changes
|
|
assert ret.changes["homedrive"] == "R:"
|
|
assert "profile" in ret.changes
|
|
assert ret.changes["profile"] == win_profile
|
|
assert "description" in ret.changes
|
|
assert ret.changes["description"] == win_description
|
|
|
|
|
|
@pytest.mark.skip_unless_on_linux(reason="underlying functionality only runs on Linux")
|
|
def test_user_present_change_groups(modules, states, username, group_1, group_2):
|
|
ret = states.user.present(
|
|
name=username,
|
|
groups=[group_1.name, group_2.name],
|
|
)
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
assert user_info["groups"] == [group_2.name, group_1.name]
|
|
|
|
# run again and remove group_2
|
|
ret = states.user.present(
|
|
name=username,
|
|
groups=[group_1.name],
|
|
)
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
assert user_info["groups"] == [group_1.name]
|
|
|
|
|
|
@pytest.mark.skip_unless_on_linux(reason="underlying functionality only runs on Linux")
|
|
def test_user_present_change_optional_groups(
|
|
modules, states, username, group_1, group_2
|
|
):
|
|
ret = states.user.present(
|
|
name=username,
|
|
optional_groups=[group_1.name, group_2.name],
|
|
)
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
assert user_info["groups"] == [group_2.name, group_1.name]
|
|
|
|
# run again and remove group_2
|
|
ret = states.user.present(
|
|
name=username,
|
|
optional_groups=[group_1.name],
|
|
)
|
|
assert ret.result is True
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
assert user_info["groups"] == [group_1.name]
|
|
|
|
|
|
@pytest.fixture
|
|
def user_present_groups(states):
|
|
groups = ["testgroup1", "testgroup2"]
|
|
try:
|
|
yield groups
|
|
finally:
|
|
for group in groups:
|
|
ret = states.group.absent(name=group)
|
|
assert ret.result is True
|
|
|
|
|
|
@pytest.mark.skip_unless_on_linux(reason="underlying functionality only runs on Linux")
|
|
def test_user_present_no_groups(modules, states, username, user_present_groups, guid):
|
|
"""
|
|
test user.present when groups arg is not
|
|
included by the group is created in another
|
|
state. Re-run the states to ensure there are
|
|
not changes and it is idempotent.
|
|
"""
|
|
ret = states.group.present(name=username, gid=guid)
|
|
assert ret.result is True
|
|
|
|
ret = states.user.present(
|
|
name=username,
|
|
uid=guid,
|
|
gid=guid,
|
|
)
|
|
assert ret.result is True
|
|
assert ret.changes["groups"] == [username]
|
|
assert ret.changes["name"] == username
|
|
|
|
ret = states.group.present(
|
|
name=user_present_groups[0],
|
|
members=[username],
|
|
)
|
|
assert ret.changes["members"] == [username]
|
|
|
|
ret = states.group.present(
|
|
name=user_present_groups[1],
|
|
members=[username],
|
|
)
|
|
assert ret.changes["members"] == [username]
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
assert user_info["groups"] == [username, *user_present_groups]
|
|
|
|
# run again, expecting no changes
|
|
ret = states.group.present(name=username)
|
|
assert ret.result is True
|
|
assert ret.changes == {}
|
|
|
|
ret = states.user.present(
|
|
name=username,
|
|
)
|
|
assert ret.result is True
|
|
assert ret.changes == {}
|
|
|
|
ret = states.group.present(
|
|
name=user_present_groups[0],
|
|
members=[username],
|
|
)
|
|
assert ret.result is True
|
|
assert ret.changes == {}
|
|
|
|
ret = states.group.present(
|
|
name=user_present_groups[1],
|
|
members=[username],
|
|
)
|
|
assert ret.result is True
|
|
assert ret.changes == {}
|
|
|
|
user_info = modules.user.info(username)
|
|
assert user_info
|
|
assert user_info["groups"] == [username, *user_present_groups]
|