diff options
Diffstat (limited to 'lib/ansible/modules/user.py')
-rw-r--r-- | lib/ansible/modules/user.py | 174 |
1 files changed, 99 insertions, 75 deletions
diff --git a/lib/ansible/modules/user.py b/lib/ansible/modules/user.py index 2fc4e473..6d465b04 100644 --- a/lib/ansible/modules/user.py +++ b/lib/ansible/modules/user.py @@ -28,11 +28,12 @@ options: comment: description: - Optionally sets the description (aka I(GECOS)) of user account. + - On macOS, this defaults to the O(name) option. type: str hidden: description: - macOS only, optionally hide the user from the login window and system preferences. - - The default will be C(true) if the I(system) option is used. + - The default will be V(true) if the O(system) option is used. type: bool version_added: "2.6" non_unique: @@ -49,28 +50,29 @@ options: group: description: - Optionally sets the user's primary group (takes a group name). + - On macOS, this defaults to V('staff') type: str groups: description: - - List of groups user will be added to. - - By default, the user is removed from all other groups. Configure C(append) to modify this. - - When set to an empty string C(''), + - A list of supplementary groups which the user is also a member of. + - By default, the user is removed from all other groups. Configure O(append) to modify this. + - When set to an empty string V(''), the user is removed from all groups except the primary group. - Before Ansible 2.3, the only input format allowed was a comma separated string. type: list elements: str append: description: - - If C(true), add the user to the groups specified in C(groups). - - If C(false), user will only be added to the groups specified in C(groups), + - If V(true), add the user to the groups specified in O(groups). + - If V(false), user will only be added to the groups specified in O(groups), removing them from all other groups. type: bool default: no shell: description: - Optionally set the user's shell. - - On macOS, before Ansible 2.5, the default shell for non-system users was C(/usr/bin/false). - Since Ansible 2.5, the default shell for non-system users on macOS is C(/bin/bash). + - On macOS, before Ansible 2.5, the default shell for non-system users was V(/usr/bin/false). + Since Ansible 2.5, the default shell for non-system users on macOS is V(/bin/bash). - See notes for details on how other operating systems determine the default shell by the underlying tool. type: str @@ -81,7 +83,7 @@ options: skeleton: description: - Optionally set a home skeleton directory. - - Requires C(create_home) option! + - Requires O(create_home) option! type: str version_added: "2.0" password: @@ -90,46 +92,51 @@ options: - B(Linux/Unix/POSIX:) Enter the hashed password as the value. - See L(FAQ entry,https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module) for details on various ways to generate the hash of a password. - - To create an account with a locked/disabled password on Linux systems, set this to C('!') or C('*'). - - To create an account with a locked/disabled password on OpenBSD, set this to C('*************'). + - To create an account with a locked/disabled password on Linux systems, set this to V('!') or V('*'). + - To create an account with a locked/disabled password on OpenBSD, set this to V('*************'). - B(OS X/macOS:) Enter the cleartext password as the value. Be sure to take relevant security precautions. + - On macOS, the password specified in the C(password) option will always be set, regardless of whether the user account already exists or not. + - When the password is passed as an argument, the C(user) module will always return changed to C(true) for macOS systems. + Since macOS no longer provides access to the hashed passwords directly. type: str state: description: - Whether the account should exist or not, taking action if the state is different from what is stated. + - See this L(FAQ entry,https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#running-on-macos-as-a-target) + for additional requirements when removing users on macOS systems. type: str choices: [ absent, present ] default: present create_home: description: - - Unless set to C(false), a home directory will be made for the user + - Unless set to V(false), a home directory will be made for the user when the account is created or if the home directory does not exist. - - Changed from C(createhome) to C(create_home) in Ansible 2.5. + - Changed from O(createhome) to O(create_home) in Ansible 2.5. type: bool default: yes aliases: [ createhome ] move_home: description: - - "If set to C(true) when used with C(home: ), attempt to move the user's old home + - "If set to V(true) when used with O(home), attempt to move the user's old home directory to the specified directory if it isn't there already and the old home exists." type: bool default: no system: description: - - When creating an account C(state=present), setting this to C(true) makes the user a system account. + - When creating an account O(state=present), setting this to V(true) makes the user a system account. - This setting cannot be changed on existing users. type: bool default: no force: description: - - This only affects C(state=absent), it forces removal of the user and associated directories on supported platforms. + - This only affects O(state=absent), it forces removal of the user and associated directories on supported platforms. - The behavior is the same as C(userdel --force), check the man page for C(userdel) on your system for details and support. - - When used with C(generate_ssh_key=yes) this forces an existing key to be overwritten. + - When used with O(generate_ssh_key=yes) this forces an existing key to be overwritten. type: bool default: no remove: description: - - This only affects C(state=absent), it attempts to remove directories associated with the user. + - This only affects O(state=absent), it attempts to remove directories associated with the user. - The behavior is the same as C(userdel --remove), check the man page for details and support. type: bool default: no @@ -140,7 +147,7 @@ options: generate_ssh_key: description: - Whether to generate a SSH key for the user in question. - - This will B(not) overwrite an existing SSH key unless used with C(force=yes). + - This will B(not) overwrite an existing SSH key unless used with O(force=yes). type: bool default: no version_added: "0.9" @@ -162,7 +169,7 @@ options: description: - Optionally specify the SSH key filename. - If this is a relative filename then it will be relative to the user's home directory. - - This parameter defaults to I(.ssh/id_rsa). + - This parameter defaults to V(.ssh/id_rsa). type: path version_added: "0.9" ssh_key_comment: @@ -179,8 +186,8 @@ options: version_added: "0.9" update_password: description: - - C(always) will update passwords if they differ. - - C(on_create) will only set the password for newly created users. + - V(always) will update passwords if they differ. + - V(on_create) will only set the password for newly created users. type: str choices: [ always, on_create ] default: always @@ -198,7 +205,7 @@ options: - Lock the password (C(usermod -L), C(usermod -U), C(pw lock)). - Implementation differs by platform. This option does not always mean the user cannot login using other methods. - This option does not disable the user, only lock the password. - - This must be set to C(False) in order to unlock a currently locked password. The absence of this parameter will not unlock a password. + - This must be set to V(False) in order to unlock a currently locked password. The absence of this parameter will not unlock a password. - Currently supported on Linux, FreeBSD, DragonFlyBSD, NetBSD, OpenBSD. type: bool version_added: "2.6" @@ -216,28 +223,25 @@ options: profile: description: - Sets the profile of the user. - - Does nothing when used with other platforms. - Can set multiple profiles using comma separation. - - To delete all the profiles, use C(profile=''). - - Currently supported on Illumos/Solaris. + - To delete all the profiles, use O(profile=''). + - Currently supported on Illumos/Solaris. Does nothing when used with other platforms. type: str version_added: "2.8" authorization: description: - Sets the authorization of the user. - - Does nothing when used with other platforms. - Can set multiple authorizations using comma separation. - - To delete all authorizations, use C(authorization=''). - - Currently supported on Illumos/Solaris. + - To delete all authorizations, use O(authorization=''). + - Currently supported on Illumos/Solaris. Does nothing when used with other platforms. type: str version_added: "2.8" role: description: - Sets the role of the user. - - Does nothing when used with other platforms. - Can set multiple roles using comma separation. - - To delete all roles, use C(role=''). - - Currently supported on Illumos/Solaris. + - To delete all roles, use O(role=''). + - Currently supported on Illumos/Solaris. Does nothing when used with other platforms. type: str version_added: "2.8" password_expire_max: @@ -252,12 +256,17 @@ options: - Supported on Linux only. type: int version_added: "2.11" + password_expire_warn: + description: + - Number of days of warning before password expires. + - Supported on Linux only. + type: int + version_added: "2.16" umask: description: - Sets the umask of the user. - - Does nothing when used with other platforms. - - Currently supported on Linux. - - Requires C(local) is omitted or False. + - Currently supported on Linux. Does nothing when used with other platforms. + - Requires O(local) is omitted or V(False). type: str version_added: "2.12" extends_documentation_fragment: action_common_attributes @@ -338,12 +347,17 @@ EXAMPLES = r''' ansible.builtin.user: name: pushkar15 password_expire_min: 5 + +- name: Set number of warning days for password expiration + ansible.builtin.user: + name: jane157 + password_expire_warn: 30 ''' RETURN = r''' append: description: Whether or not to append the user to groups. - returned: When state is C(present) and the user exists + returned: When O(state) is V(present) and the user exists type: bool sample: True comment: @@ -358,7 +372,7 @@ create_home: sample: True force: description: Whether or not a user account was forcibly deleted. - returned: When I(state) is C(absent) and user exists + returned: When O(state) is V(absent) and user exists type: bool sample: False group: @@ -368,17 +382,17 @@ group: sample: 1001 groups: description: List of groups of which the user is a member. - returned: When I(groups) is not empty and I(state) is C(present) + returned: When O(groups) is not empty and O(state) is V(present) type: str sample: 'chrony,apache' home: description: "Path to user's home directory." - returned: When I(state) is C(present) + returned: When O(state) is V(present) type: str sample: '/home/asmith' move_home: description: Whether or not to move an existing home directory. - returned: When I(state) is C(present) and user exists + returned: When O(state) is V(present) and user exists type: bool sample: False name: @@ -388,32 +402,32 @@ name: sample: asmith password: description: Masked value of the password. - returned: When I(state) is C(present) and I(password) is not empty + returned: When O(state) is V(present) and O(password) is not empty type: str sample: 'NOT_LOGGING_PASSWORD' remove: description: Whether or not to remove the user account. - returned: When I(state) is C(absent) and user exists + returned: When O(state) is V(absent) and user exists type: bool sample: True shell: description: User login shell. - returned: When I(state) is C(present) + returned: When O(state) is V(present) type: str sample: '/bin/bash' ssh_fingerprint: description: Fingerprint of generated SSH key. - returned: When I(generate_ssh_key) is C(True) + returned: When O(generate_ssh_key) is V(True) type: str sample: '2048 SHA256:aYNHYcyVm87Igh0IMEDMbvW0QDlRQfE0aJugp684ko8 ansible-generated on host (RSA)' ssh_key_file: description: Path to generated SSH private key file. - returned: When I(generate_ssh_key) is C(True) + returned: When O(generate_ssh_key) is V(True) type: str sample: /home/asmith/.ssh/id_rsa ssh_public_key: description: Generated SSH public key file. - returned: When I(generate_ssh_key) is C(True) + returned: When O(generate_ssh_key) is V(True) type: str sample: > 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC95opt4SPEC06tOYsJQJIuN23BbLMGmYo8ysVZQc4h2DZE9ugbjWWGS1/pweUGjVstgzMkBEeBCByaEf/RJKNecKRPeGd2Bw9DCj/bn5Z6rGfNENKBmo @@ -431,30 +445,18 @@ stdout: sample: system: description: Whether or not the account is a system account. - returned: When I(system) is passed to the module and the account does not exist + returned: When O(system) is passed to the module and the account does not exist type: bool sample: True uid: description: User ID of the user account. - returned: When I(uid) is passed to the module + returned: When O(uid) is passed to the module type: int sample: 1044 -password_expire_max: - description: Maximum number of days during which a password is valid. - returned: When user exists - type: int - sample: 20 -password_expire_min: - description: Minimum number of days between password change - returned: When user exists - type: int - sample: 20 ''' -import ctypes import ctypes.util -import errno import grp import calendar import os @@ -469,7 +471,7 @@ import time import math from ansible.module_utils import distro -from ansible.module_utils._text import to_bytes, to_native, to_text +from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.common.locale import get_best_parsable_locale from ansible.module_utils.common.sys_info import get_platform_subclass @@ -574,6 +576,7 @@ class User(object): self.role = module.params['role'] self.password_expire_max = module.params['password_expire_max'] self.password_expire_min = module.params['password_expire_min'] + self.password_expire_warn = module.params['password_expire_warn'] self.umask = module.params['umask'] if self.umask is not None and self.local: @@ -867,7 +870,7 @@ class User(object): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set(remove_existing=False) + groups = self.get_groups_set(remove_existing=False, names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -913,7 +916,8 @@ class User(object): if self.expires is not None: - current_expires = int(self.user_password()[1]) + current_expires = self.user_password()[1] or '0' + current_expires = int(current_expires) if self.expires < time.gmtime(0): if current_expires >= 0: @@ -1008,16 +1012,22 @@ class User(object): except (ValueError, KeyError): return list(grp.getgrnam(group)) - def get_groups_set(self, remove_existing=True): + def get_groups_set(self, remove_existing=True, names_only=False): if self.groups is None: return None info = self.user_info() groups = set(x.strip() for x in self.groups.split(',') if x) + group_names = set() for g in groups.copy(): if not self.group_exists(g): self.module.fail_json(msg="Group %s does not exist" % (g)) - if info and remove_existing and self.group_info(g)[2] == info[3]: + group_info = self.group_info(g) + if info and remove_existing and group_info[2] == info[3]: groups.remove(g) + elif names_only: + group_names.add(group_info[0]) + if names_only: + return group_names return groups def user_group_membership(self, exclude_primary=True): @@ -1084,6 +1094,7 @@ class User(object): def set_password_expire(self): min_needs_change = self.password_expire_min is not None max_needs_change = self.password_expire_max is not None + warn_needs_change = self.password_expire_warn is not None if HAVE_SPWD: try: @@ -1093,8 +1104,9 @@ class User(object): min_needs_change &= self.password_expire_min != shadow_info.sp_min max_needs_change &= self.password_expire_max != shadow_info.sp_max + warn_needs_change &= self.password_expire_warn != shadow_info.sp_warn - if not (min_needs_change or max_needs_change): + if not (min_needs_change or max_needs_change or warn_needs_change): return (None, '', '') # target state already reached command_name = 'chage' @@ -1103,6 +1115,8 @@ class User(object): cmd.extend(["-m", self.password_expire_min]) if max_needs_change: cmd.extend(["-M", self.password_expire_max]) + if warn_needs_change: + cmd.extend(["-W", self.password_expire_warn]) cmd.append(self.name) return self.execute_command(cmd) @@ -1277,7 +1291,7 @@ class User(object): else: skeleton = '/etc/skel' - if os.path.exists(skeleton): + if os.path.exists(skeleton) and skeleton != os.devnull: try: shutil.copytree(skeleton, path, symlinks=True) except OSError as e: @@ -1523,7 +1537,7 @@ class FreeBsdUser(User): if self.groups is not None: current_groups = self.user_group_membership() - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) groups_need_mod = False @@ -1546,7 +1560,8 @@ class FreeBsdUser(User): if self.expires is not None: - current_expires = int(self.user_password()[1]) + current_expires = self.user_password()[1] or '0' + current_expires = int(current_expires) # If expiration is negative or zero and the current expiration is greater than zero, disable expiration. # In OpenBSD, setting expiration to zero disables expiration. It does not expire the account. @@ -1717,7 +1732,7 @@ class OpenBSDUser(User): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -1893,7 +1908,7 @@ class NetBSDUser(User): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -2127,7 +2142,7 @@ class SunOS(User): if self.groups is not None: current_groups = self.user_group_membership() - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) groups_need_mod = False @@ -2404,7 +2419,7 @@ class DarwinUser(User): current = set(self._list_user_groups()) if self.groups is not None: - target = set(self.groups.split(',')) + target = self.get_groups_set(names_only=True) else: target = set([]) @@ -2498,6 +2513,14 @@ class DarwinUser(User): if rc != 0: self.module.fail_json(msg='Cannot create user "%s".' % self.name, err=err, out=out, rc=rc) + # Make the Gecos (alias display name) default to username + if self.comment is None: + self.comment = self.name + + # Make user group default to 'staff' + if self.group is None: + self.group = 'staff' + self._make_group_numerical() if self.uid is None: self.uid = str(self._get_next_uid(self.system)) @@ -2688,7 +2711,7 @@ class AIX(User): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set() + groups = self.get_groups_set(names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -2886,7 +2909,7 @@ class HPUX(User): if current_groups and not self.append: groups_need_mod = True else: - groups = self.get_groups_set(remove_existing=False) + groups = self.get_groups_set(remove_existing=False, names_only=True) group_diff = set(current_groups).symmetric_difference(groups) if group_diff: @@ -3096,6 +3119,7 @@ def main(): login_class=dict(type='str'), password_expire_max=dict(type='int', no_log=False), password_expire_min=dict(type='int', no_log=False), + password_expire_warn=dict(type='int', no_log=False), # following options are specific to macOS hidden=dict(type='bool'), # following options are specific to selinux |