PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/modules/mac_user.py |
""" Manage users on Mac OS 10.7+ .. important:: If you feel that Salt should be using this module to manage users on a minion, and it is using a different module (or gives an error similar to *'user.info' is not available*), see :ref:`here <module-provider-override>`. """ import logging import time import salt.utils.args import salt.utils.data import salt.utils.decorators.path import salt.utils.files import salt.utils.stringutils import salt.utils.user from salt.exceptions import CommandExecutionError, SaltInvocationError try: import pwd except ImportError: pass log = logging.getLogger(__name__) # Define the module's virtual name __virtualname__ = "user" def __virtual__(): if __grains__.get("kernel") != "Darwin" or __grains__["osrelease_info"] < (10, 7): return (False, "Only available on Mac OS 10.7+ systems") else: return __virtualname__ def _flush_dscl_cache(): """ Flush dscl cache """ __salt__["cmd.run"](["dscacheutil", "-flushcache"], python_shell=False) def _dscl(cmd, ctype="create"): """ Run a dscl -create command """ if __grains__["osrelease_info"] < (10, 8): source, noderoot = ".", "" else: source, noderoot = "localhost", "/Local/Default" if noderoot: cmd[0] = noderoot + cmd[0] return __salt__["cmd.run_all"]( ["dscl", source, "-" + ctype] + cmd, output_loglevel="quiet" if ctype == "passwd" else "debug", python_shell=False, ) def _first_avail_uid(): uids = {x.pw_uid for x in pwd.getpwall()} for idx in range(501, 2**24): if idx not in uids: return idx def add( name, uid=None, gid=None, groups=None, home=None, shell=None, fullname=None, createhome=True, **kwargs, ): """ Add a user to the minion CLI Example: .. code-block:: bash salt '*' user.add name <uid> <gid> <groups> <home> <shell> """ if info(name): raise CommandExecutionError(f"User '{name}' already exists") if salt.utils.stringutils.contains_whitespace(name): raise SaltInvocationError("Username cannot contain whitespace") if uid is None: uid = _first_avail_uid() if gid is None: gid = 20 # gid 20 == 'staff', the default group if home is None: home = f"/Users/{name}" if shell is None: shell = "/bin/bash" if fullname is None: fullname = "" if not isinstance(uid, int): raise SaltInvocationError("uid must be an integer") if not isinstance(gid, int): raise SaltInvocationError("gid must be an integer") name_path = f"/Users/{name}" _dscl([name_path, "UniqueID", uid]) _dscl([name_path, "PrimaryGroupID", gid]) _dscl([name_path, "UserShell", shell]) _dscl([name_path, "NFSHomeDirectory", home]) _dscl([name_path, "RealName", fullname]) # Make sure home directory exists if createhome: __salt__["file.mkdir"](home, user=uid, group=gid) # dscl buffers changes, sleep before setting group membership time.sleep(1) if groups: chgroups(name, groups) return True def delete(name, remove=False, force=False): """ Remove a user from the minion CLI Example: .. code-block:: bash salt '*' user.delete name remove=True force=True """ if salt.utils.stringutils.contains_whitespace(name): raise SaltInvocationError("Username cannot contain whitespace") user_info = info(name) if not user_info: return True # force is added for compatibility with user.absent state function if force: log.warning("force option is unsupported on MacOS, ignoring") # Remove from any groups other than primary group. Needs to be done since # group membership is managed separately from users and an entry for the # user will persist even after the user is removed. chgroups(name, ()) ret = _dscl([f"/Users/{name}"], ctype="delete")["retcode"] == 0 if ret and remove: # remove home directory from filesystem __salt__["file.remove"](user_info["home"]) return ret def getent(refresh=False): """ Return the list of all info for all users CLI Example: .. code-block:: bash salt '*' user.getent """ if "user.getent" in __context__ and not refresh: return __context__["user.getent"] ret = [] for data in pwd.getpwall(): ret.append(_format_info(data)) __context__["user.getent"] = ret return ret def chuid(name, uid): """ Change the uid for a named user CLI Example: .. code-block:: bash salt '*' user.chuid foo 4376 """ if not isinstance(uid, int): raise SaltInvocationError("uid must be an integer") pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") if uid == pre_info["uid"]: return True _dscl([f"/Users/{name}", "UniqueID", pre_info["uid"], uid], ctype="change") # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(name).get("uid") == uid def chgid(name, gid): """ Change the default group of the user CLI Example: .. code-block:: bash salt '*' user.chgid foo 4376 """ if not isinstance(gid, int): raise SaltInvocationError("gid must be an integer") pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") if gid == pre_info["gid"]: return True _dscl( [f"/Users/{name}", "PrimaryGroupID", pre_info["gid"], gid], ctype="change", ) # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(name).get("gid") == gid def chshell(name, shell): """ Change the default shell of the user CLI Example: .. code-block:: bash salt '*' user.chshell foo /bin/zsh """ pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") if shell == pre_info["shell"]: return True _dscl( [f"/Users/{name}", "UserShell", pre_info["shell"], shell], ctype="change", ) # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(name).get("shell") == shell def chhome(name, home, **kwargs): """ Change the home directory of the user CLI Example: .. code-block:: bash salt '*' user.chhome foo /Users/foo """ kwargs = salt.utils.args.clean_kwargs(**kwargs) persist = kwargs.pop("persist", False) if kwargs: salt.utils.args.invalid_kwargs(kwargs) if persist: log.info("Ignoring unsupported 'persist' argument to user.chhome") pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") if home == pre_info["home"]: return True _dscl( [f"/Users/{name}", "NFSHomeDirectory", pre_info["home"], home], ctype="change", ) # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(name).get("home") == home def chfullname(name, fullname): """ Change the user's Full Name CLI Example: .. code-block:: bash salt '*' user.chfullname foo 'Foo Bar' """ fullname = salt.utils.data.decode(fullname) pre_info = info(name) if not pre_info: raise CommandExecutionError(f"User '{name}' does not exist") pre_info["fullname"] = salt.utils.data.decode(pre_info["fullname"]) if fullname == pre_info["fullname"]: return True _dscl( [f"/Users/{name}", "RealName", fullname], # use a 'create' command, because a 'change' command would fail if # current fullname is an empty string. The 'create' will just overwrite # this field. ctype="create", ) # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) current = salt.utils.data.decode(info(name).get("fullname")) return current == fullname def chgroups(name, groups, append=False): """ Change the groups to which the user belongs. Note that the user's primary group does not have to be one of the groups passed, membership in the user's primary group is automatically assumed. groups Groups to which the user should belong, can be passed either as a python list or a comma-separated string append Instead of removing user from groups not included in the ``groups`` parameter, just add user to any groups for which they are not members CLI Example: .. code-block:: bash salt '*' user.chgroups foo wheel,root """ ### NOTE: **args isn't used here but needs to be included in this ### function for compatibility with the user.present state uinfo = info(name) if not uinfo: raise CommandExecutionError(f"User '{name}' does not exist") if isinstance(groups, str): groups = groups.split(",") bad_groups = [x for x in groups if salt.utils.stringutils.contains_whitespace(x)] if bad_groups: raise SaltInvocationError( "Invalid group name(s): {}".format(", ".join(bad_groups)) ) ugrps = set(list_groups(name)) desired = {str(x) for x in groups if bool(str(x))} primary_group = __salt__["file.gid_to_group"](uinfo["gid"]) if primary_group: desired.add(primary_group) if ugrps == desired: return True # Add groups from which user is missing for group in desired - ugrps: _dscl([f"/Groups/{group}", "GroupMembership", name], ctype="append") if not append: # Remove from extra groups for group in ugrps - desired: _dscl([f"/Groups/{group}", "GroupMembership", name], ctype="delete") time.sleep(1) return set(list_groups(name)) == desired def info(name): """ Return user information CLI Example: .. code-block:: bash salt '*' user.info root """ try: # pwd.getpwnam seems to cache weirdly, after an account is # deleted, it still returns data. Let's not use it data = next(iter(x for x in pwd.getpwall() if x.pw_name == name)) except StopIteration: return {} else: return _format_info(data) def _format_info(data): """ Return user information in a pretty way """ return { "gid": data.pw_gid, "groups": list_groups(data.pw_name), "home": data.pw_dir, "name": data.pw_name, "shell": data.pw_shell, "uid": data.pw_uid, "fullname": data.pw_gecos, } @salt.utils.decorators.path.which("id") def primary_group(name): """ Return the primary group of the named user .. versionadded:: 2016.3.0 CLI Example: .. code-block:: bash salt '*' user.primary_group saltadmin """ return __salt__["cmd.run"](["id", "-g", "-n", name]) def list_groups(name): """ Return a list of groups the named user belongs to. name The name of the user for which to list groups. Starting in Salt 2016.11.0, all groups for the user, including groups beginning with an underscore will be listed. .. versionchanged:: 2016.11.0 CLI Example: .. code-block:: bash salt '*' user.list_groups foo """ groups = [group for group in salt.utils.user.get_group_list(name)] return groups def list_users(): """ Return a list of all users CLI Example: .. code-block:: bash salt '*' user.list_users """ users = _dscl(["/users"], "list")["stdout"] return users.split() def rename(name, new_name): """ Change the username for a named user CLI Example: .. code-block:: bash salt '*' user.rename name new_name """ current_info = info(name) if not current_info: raise CommandExecutionError(f"User '{name}' does not exist") new_info = info(new_name) if new_info: raise CommandExecutionError(f"User '{new_name}' already exists") _dscl([f"/Users/{name}", "RecordName", name, new_name], ctype="change") # dscl buffers changes, sleep 1 second before checking if new value # matches desired value time.sleep(1) return info(new_name).get("RecordName") == new_name def get_auto_login(): """ .. versionadded:: 2016.3.0 Gets the current setting for Auto Login :return: If enabled, returns the user name, otherwise returns False :rtype: str, bool CLI Example: .. code-block:: bash salt '*' user.get_auto_login """ cmd = [ "defaults", "read", "/Library/Preferences/com.apple.loginwindow.plist", "autoLoginUser", ] ret = __salt__["cmd.run_all"](cmd, ignore_retcode=True) return False if ret["retcode"] else ret["stdout"] 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) + 1 # macOS adds an extra byte for the trailing null # Convert each character to a byte and add a trailing null password = list(map(ord, password)) + [0] # 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 # Return the raw bytes return bytes(password) def enable_auto_login(name, password): """ .. versionadded:: 2016.3.0 Configures the machine to auto login with the specified user Args: 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: .. code-block:: bash salt '*' user.enable_auto_login stevej """ # Make the entry into the defaults file cmd = [ "defaults", "write", "/Library/Preferences/com.apple.loginwindow.plist", "autoLoginUser", 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.files.fopen("/etc/kcpassword", "wb") as fd: fd.write(o_password) return current if isinstance(current, bool) else current.lower() == name.lower() def disable_auto_login(): """ .. versionadded:: 2016.3.0 Disables auto login on the machine Returns: bool: ``True`` if successful, otherwise ``False`` CLI Example: .. code-block:: bash 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", "autoLoginUser", ] __salt__["cmd.run"](cmd) return True if not get_auto_login() else False