summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/user.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/modules/user.py')
-rw-r--r--lib/ansible/modules/user.py174
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