mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
User unexpire (#39758)
* Allow negative values to expires to unexpire a user Fixes #20096 (cherry picked from commit 34f8080a19c09cd20ec9c045fca1e37ef74bb1e6) (cherry picked from commit 54619f70f4b79f121c5062d54e9732d3cbb24377) (cherry picked from commit 8c2fae27d6e2af810112032bb1dfef5459035b7e) (cherry picked from commit db1a32f8caa8c8b9f989baa65784d4b2b5cad1f8) * tweaked and normalized - also added tests, made checking resilient
This commit is contained in:
parent
19356c03e8
commit
677fe1076d
2 changed files with 116 additions and 46 deletions
|
@ -32,12 +32,12 @@ options:
|
||||||
- Name of the user to create, remove or modify.
|
- Name of the user to create, remove or modify.
|
||||||
required: true
|
required: true
|
||||||
aliases: [ user ]
|
aliases: [ user ]
|
||||||
comment:
|
|
||||||
description:
|
|
||||||
- Optionally sets the description (aka I(GECOS)) of user account.
|
|
||||||
uid:
|
uid:
|
||||||
description:
|
description:
|
||||||
- Optionally sets the I(UID) of the user.
|
- Optionally sets the I(UID) of the user.
|
||||||
|
comment:
|
||||||
|
description:
|
||||||
|
- Optionally sets the description (aka I(GECOS)) of user account.
|
||||||
hidden:
|
hidden:
|
||||||
required: false
|
required: false
|
||||||
type: bool
|
type: bool
|
||||||
|
@ -47,8 +47,7 @@ options:
|
||||||
version_added: "2.6"
|
version_added: "2.6"
|
||||||
non_unique:
|
non_unique:
|
||||||
description:
|
description:
|
||||||
- Optionally when used with the -u option, this option allows to
|
- Optionally when used with the -u option, this option allows to change the user ID to a non-unique value.
|
||||||
change the user ID to a non-unique value.
|
|
||||||
type: bool
|
type: bool
|
||||||
default: "no"
|
default: "no"
|
||||||
version_added: "1.1"
|
version_added: "1.1"
|
||||||
|
@ -67,16 +66,14 @@ options:
|
||||||
now it should be able to accept YAML lists also.
|
now it should be able to accept YAML lists also.
|
||||||
append:
|
append:
|
||||||
description:
|
description:
|
||||||
- If C(yes), will only add groups, not set them to just the list
|
- If C(yes), will only add groups, not set them to just the list in I(groups).
|
||||||
in I(groups).
|
|
||||||
type: bool
|
type: bool
|
||||||
default: "no"
|
default: "no"
|
||||||
shell:
|
shell:
|
||||||
description:
|
description:
|
||||||
- Optionally set the user's shell.
|
- Optionally set the user's shell.
|
||||||
- On Mac OS X, before version 2.5, the default shell for non-system users was
|
- On Mac OS X, before version 2.5, the default shell for non-system users was /usr/bin/false.
|
||||||
/usr/bin/false. Since 2.5, the default shell for non-system users on
|
Since 2.5, the default shell for non-system users on Mac OS X is /bin/bash.
|
||||||
Mac OS X is /bin/bash.
|
|
||||||
home:
|
home:
|
||||||
description:
|
description:
|
||||||
- Optionally set the user's home directory.
|
- Optionally set the user's home directory.
|
||||||
|
@ -98,39 +95,38 @@ options:
|
||||||
create_home:
|
create_home:
|
||||||
description:
|
description:
|
||||||
- Unless set to C(no), a home directory will be made for the user
|
- Unless set to C(no), a home directory will be made for the user
|
||||||
when the account is created or if the home directory does not
|
when the account is created or if the home directory does not exist.
|
||||||
exist.
|
|
||||||
- Changed from C(createhome) to C(create_home) in version 2.5.
|
- Changed from C(createhome) to C(create_home) in version 2.5.
|
||||||
type: bool
|
type: bool
|
||||||
default: 'yes'
|
default: 'yes'
|
||||||
aliases: ['createhome']
|
aliases: ['createhome']
|
||||||
move_home:
|
move_home:
|
||||||
description:
|
description:
|
||||||
- If set to C(yes) when used with C(home=), attempt to move the
|
- If set to C(yes) when used with C(home=), attempt to move the user's old home
|
||||||
user's home directory to the specified directory if it isn't there
|
directory to the specified directory if it isn't there already and the old home exists.
|
||||||
already.
|
|
||||||
type: bool
|
type: bool
|
||||||
default: "no"
|
default: "no"
|
||||||
system:
|
system:
|
||||||
description:
|
description:
|
||||||
- When creating an account, setting this to C(yes) makes the user a
|
- When creating an account C(state=present), setting this to C(yes) makes the user a system account.
|
||||||
system account. This setting cannot be changed on existing users.
|
This setting cannot be changed on existing users.
|
||||||
type: bool
|
type: bool
|
||||||
default: "no"
|
default: "no"
|
||||||
force:
|
force:
|
||||||
description:
|
description:
|
||||||
- When used with C(state=absent), behavior is as with C(userdel --force).
|
- This only affects C(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.
|
||||||
|
type: bool
|
||||||
|
default: "no"
|
||||||
|
remove:
|
||||||
|
description:
|
||||||
|
- This only affects C(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
|
type: bool
|
||||||
default: "no"
|
default: "no"
|
||||||
login_class:
|
login_class:
|
||||||
description:
|
description:
|
||||||
- Optionally sets the user's login class for FreeBSD, DragonFlyBSD, OpenBSD and
|
- Optionally sets the user's login class, a feature of most BSD OSs.
|
||||||
NetBSD systems.
|
|
||||||
remove:
|
|
||||||
description:
|
|
||||||
- When used with C(state=absent), behavior is as with C(userdel --remove).
|
|
||||||
type: bool
|
|
||||||
default: "no"
|
|
||||||
generate_ssh_key:
|
generate_ssh_key:
|
||||||
description:
|
description:
|
||||||
- Whether to generate a SSH key for the user in question.
|
- Whether to generate a SSH key for the user in question.
|
||||||
|
@ -176,7 +172,8 @@ options:
|
||||||
expires:
|
expires:
|
||||||
description:
|
description:
|
||||||
- An expiry time for the user in epoch, it will be ignored on platforms that do not support this.
|
- An expiry time for the user in epoch, it will be ignored on platforms that do not support this.
|
||||||
Currently supported on Linux, FreeBSD, and DragonFlyBSD.
|
Currently supported on GNU/Linux, FreeBSD, and DragonFlyBSD.
|
||||||
|
- Since version 2.6 you can remove the expiry time specify a negative value. Currently supported on GNU/Linux and FreeBSD.
|
||||||
version_added: "1.9"
|
version_added: "1.9"
|
||||||
password_lock:
|
password_lock:
|
||||||
description:
|
description:
|
||||||
|
@ -231,6 +228,12 @@ EXAMPLES = '''
|
||||||
shell: /bin/zsh
|
shell: /bin/zsh
|
||||||
groups: developers
|
groups: developers
|
||||||
expires: 1422403387
|
expires: 1422403387
|
||||||
|
|
||||||
|
- name: starting at version 2.6, modify user, remove expiry time
|
||||||
|
user:
|
||||||
|
name: james18
|
||||||
|
expires: -1
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import grp
|
import grp
|
||||||
|
@ -311,11 +314,11 @@ class User(object):
|
||||||
if module.params['groups'] is not None:
|
if module.params['groups'] is not None:
|
||||||
self.groups = ','.join(module.params['groups'])
|
self.groups = ','.join(module.params['groups'])
|
||||||
|
|
||||||
if module.params['expires']:
|
if module.params['expires'] is not None:
|
||||||
try:
|
try:
|
||||||
self.expires = time.gmtime(module.params['expires'])
|
self.expires = time.gmtime(module.params['expires'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
module.fail_json(msg="Invalid expires time %s: %s" % (self.expires, to_native(e)))
|
module.fail_json(msg="Invalid value for 'expires' %s: %s" % (self.expires, to_native(e)))
|
||||||
|
|
||||||
if module.params['ssh_key_file'] is not None:
|
if module.params['ssh_key_file'] is not None:
|
||||||
self.ssh_file = module.params['ssh_key_file']
|
self.ssh_file = module.params['ssh_key_file']
|
||||||
|
@ -409,7 +412,7 @@ class User(object):
|
||||||
cmd.append('-s')
|
cmd.append('-s')
|
||||||
cmd.append(self.shell)
|
cmd.append(self.shell)
|
||||||
|
|
||||||
if self.expires:
|
if self.expires is not None:
|
||||||
cmd.append('-e')
|
cmd.append('-e')
|
||||||
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
||||||
|
|
||||||
|
@ -532,17 +535,22 @@ class User(object):
|
||||||
cmd.append('-s')
|
cmd.append('-s')
|
||||||
cmd.append(self.shell)
|
cmd.append(self.shell)
|
||||||
|
|
||||||
if self.expires:
|
if self.expires is not None:
|
||||||
current_expires = self.user_password()[1]
|
|
||||||
|
|
||||||
# Convert days since Epoch to seconds since Epoch as struct_time
|
current_expires = int(self.user_password()[1])
|
||||||
total_seconds = int(current_expires) * 86400
|
|
||||||
current_expires = time.gmtime(total_seconds)
|
|
||||||
|
|
||||||
# Compare year, month, and day only
|
if self.expires < time.gmtime(0):
|
||||||
if current_expires[:3] != self.expires[:3]:
|
if current_expires > 0:
|
||||||
cmd.append('-e')
|
cmd.append('-e')
|
||||||
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
cmd.append('')
|
||||||
|
else:
|
||||||
|
# Convert days since Epoch to seconds since Epoch as struct_time
|
||||||
|
current_expire_date = time.gmtime(current_expires * 86400)
|
||||||
|
|
||||||
|
# Current expires is negative or we compare year, month, and day only
|
||||||
|
if current_expires <= 0 or current_expire_date[:3] != self.expires[:3]:
|
||||||
|
cmd.append('-e')
|
||||||
|
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
||||||
|
|
||||||
if self.password_lock:
|
if self.password_lock:
|
||||||
cmd.append('-L')
|
cmd.append('-L')
|
||||||
|
@ -647,7 +655,7 @@ class User(object):
|
||||||
for line in open(self.SHADOWFILE).readlines():
|
for line in open(self.SHADOWFILE).readlines():
|
||||||
if line.startswith('%s:' % self.name):
|
if line.startswith('%s:' % self.name):
|
||||||
passwd = line.split(':')[1]
|
passwd = line.split(':')[1]
|
||||||
expires = line.split(':')[self.SHADOWFILE_EXPIRE_INDEX]
|
expires = line.split(':')[self.SHADOWFILE_EXPIRE_INDEX] or -1
|
||||||
return passwd, expires
|
return passwd, expires
|
||||||
|
|
||||||
def get_ssh_key_path(self):
|
def get_ssh_key_path(self):
|
||||||
|
@ -845,7 +853,7 @@ class FreeBsdUser(User):
|
||||||
cmd.append('-L')
|
cmd.append('-L')
|
||||||
cmd.append(self.login_class)
|
cmd.append(self.login_class)
|
||||||
|
|
||||||
if self.expires:
|
if self.expires is not None:
|
||||||
cmd.append('-e')
|
cmd.append('-e')
|
||||||
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
||||||
|
|
||||||
|
@ -946,13 +954,22 @@ class FreeBsdUser(User):
|
||||||
new_groups = groups | set(current_groups)
|
new_groups = groups | set(current_groups)
|
||||||
cmd.append(','.join(new_groups))
|
cmd.append(','.join(new_groups))
|
||||||
|
|
||||||
if self.expires:
|
if self.expires is not None:
|
||||||
current_expires = time.gmtime(int(self.user_password()[1]))
|
|
||||||
|
|
||||||
# Compare year, month, and day only
|
current_expires = int(self.user_password()[1])
|
||||||
if current_expires[:3] != self.expires[:3]:
|
|
||||||
cmd.append('-e')
|
if self.expires < time.gmtime(0):
|
||||||
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
if current_expires > 0:
|
||||||
|
cmd.append('-e')
|
||||||
|
cmd.append('0')
|
||||||
|
else:
|
||||||
|
# Convert days since Epoch to seconds since Epoch as struct_time
|
||||||
|
current_expire_date = time.gmtime(current_expires)
|
||||||
|
|
||||||
|
# Current expires is negative or we compare year, month, and day only
|
||||||
|
if current_expires <= 0 or current_expire_date[:3] != self.expires[:3]:
|
||||||
|
cmd.append('-e')
|
||||||
|
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
|
||||||
|
|
||||||
# modify the user if cmd will do anything
|
# modify the user if cmd will do anything
|
||||||
if cmd_len != len(cmd):
|
if cmd_len != len(cmd):
|
||||||
|
|
|
@ -246,3 +246,56 @@
|
||||||
- name: Restore original timezone - {{ original_timezone.diff.before.name }}
|
- name: Restore original timezone - {{ original_timezone.diff.before.name }}
|
||||||
timezone:
|
timezone:
|
||||||
name: "{{ original_timezone.diff.before.name }}"
|
name: "{{ original_timezone.diff.before.name }}"
|
||||||
|
|
||||||
|
|
||||||
|
- name: Unexpire user
|
||||||
|
user:
|
||||||
|
name: ansibulluser
|
||||||
|
state: present
|
||||||
|
expires: -1
|
||||||
|
register: user_test_expires3
|
||||||
|
|
||||||
|
- name: Verify un expiration date for Linux
|
||||||
|
block:
|
||||||
|
- name: LINUX | Get expiration date for ansibulluser
|
||||||
|
getent:
|
||||||
|
database: shadow
|
||||||
|
key: ansibulluser
|
||||||
|
|
||||||
|
- name: LINUX | Ensure proper expiration date was set
|
||||||
|
assert:
|
||||||
|
msg: "expiry is supposed to be empty or -1, not {{getent_shadow['ansibulluser'][6]}}"
|
||||||
|
that:
|
||||||
|
- not getent_shadow['ansibulluser'][6] or getent_shadow['ansibulluser'][6] < 0
|
||||||
|
when: ansible_os_family in ['RedHat', 'Debian', 'Suse']
|
||||||
|
|
||||||
|
- name: Verify un expiration date for linux/BSD
|
||||||
|
block:
|
||||||
|
- name: Unexpire user again to check for change
|
||||||
|
user:
|
||||||
|
name: ansibulluser
|
||||||
|
state: present
|
||||||
|
expires: -1
|
||||||
|
register: user_test_expires4
|
||||||
|
|
||||||
|
- name: Ensure first expiration reported a change and second did not
|
||||||
|
assert:
|
||||||
|
msg: The second run of the expiration removal task reported a change when it should not
|
||||||
|
that:
|
||||||
|
- user_test_expires3 is changed
|
||||||
|
- user_test_expires4 is not changed
|
||||||
|
when: ansible_os_family in ['RedHat', 'Debian', 'Suse', 'FreeBSD']
|
||||||
|
|
||||||
|
- name: Verify un expiration date for BSD
|
||||||
|
block:
|
||||||
|
- name: BSD | Get expiration date for ansibulluser
|
||||||
|
shell: 'grep ansibulluser /etc/master.passwd | cut -d: -f 7'
|
||||||
|
changed_when: no
|
||||||
|
register: bsd_account_expiration
|
||||||
|
|
||||||
|
- name: BSD | Ensure proper expiration date was set
|
||||||
|
assert:
|
||||||
|
msg: "expiry is supposed to be '0', not {{bsd_account_expiration.stdout}}"
|
||||||
|
that:
|
||||||
|
- bsd_account_expiration.stdout == '0'
|
||||||
|
when: ansible_os_family == 'FreeBSD'
|
||||||
|
|
Loading…
Reference in a new issue