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:
Brian Coca 2018-05-17 11:34:13 -04:00 committed by GitHub
parent 19356c03e8
commit 677fe1076d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 116 additions and 46 deletions

View file

@ -32,12 +32,12 @@ options:
- Name of the user to create, remove or modify.
required: true
aliases: [ user ]
comment:
description:
- Optionally sets the description (aka I(GECOS)) of user account.
uid:
description:
- Optionally sets the I(UID) of the user.
comment:
description:
- Optionally sets the description (aka I(GECOS)) of user account.
hidden:
required: false
type: bool
@ -47,8 +47,7 @@ options:
version_added: "2.6"
non_unique:
description:
- Optionally when used with the -u option, this option allows to
change the user ID to a non-unique value.
- Optionally when used with the -u option, this option allows to change the user ID to a non-unique value.
type: bool
default: "no"
version_added: "1.1"
@ -67,16 +66,14 @@ options:
now it should be able to accept YAML lists also.
append:
description:
- If C(yes), will only add groups, not set them to just the list
in I(groups).
- If C(yes), will only add groups, not set them to just the list in I(groups).
type: bool
default: "no"
shell:
description:
- Optionally set the user's shell.
- On Mac OS X, before version 2.5, the default shell for non-system users was
/usr/bin/false. Since 2.5, the default shell for non-system users on
Mac OS X is /bin/bash.
- On Mac OS X, before version 2.5, the default shell for non-system users was /usr/bin/false.
Since 2.5, the default shell for non-system users on Mac OS X is /bin/bash.
home:
description:
- Optionally set the user's home directory.
@ -98,39 +95,38 @@ options:
create_home:
description:
- 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
exist.
when the account is created or if the home directory does not exist.
- Changed from C(createhome) to C(create_home) in version 2.5.
type: bool
default: 'yes'
aliases: ['createhome']
move_home:
description:
- If set to C(yes) when used with C(home=), attempt to move the
user's home directory to the specified directory if it isn't there
already.
- If set to C(yes) when used with C(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, setting this to C(yes) makes the user a
system account. This setting cannot be changed on existing users.
- When creating an account C(state=present), setting this to C(yes) makes the user a system account.
This setting cannot be changed on existing users.
type: bool
default: "no"
force:
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
default: "no"
login_class:
description:
- Optionally sets the user's login class for FreeBSD, DragonFlyBSD, OpenBSD and
NetBSD systems.
remove:
description:
- When used with C(state=absent), behavior is as with C(userdel --remove).
type: bool
default: "no"
- Optionally sets the user's login class, a feature of most BSD OSs.
generate_ssh_key:
description:
- Whether to generate a SSH key for the user in question.
@ -176,7 +172,8 @@ options:
expires:
description:
- 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"
password_lock:
description:
@ -231,6 +228,12 @@ EXAMPLES = '''
shell: /bin/zsh
groups: developers
expires: 1422403387
- name: starting at version 2.6, modify user, remove expiry time
user:
name: james18
expires: -1
'''
import grp
@ -311,11 +314,11 @@ class User(object):
if module.params['groups'] is not None:
self.groups = ','.join(module.params['groups'])
if module.params['expires']:
if module.params['expires'] is not None:
try:
self.expires = time.gmtime(module.params['expires'])
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:
self.ssh_file = module.params['ssh_key_file']
@ -409,7 +412,7 @@ class User(object):
cmd.append('-s')
cmd.append(self.shell)
if self.expires:
if self.expires is not None:
cmd.append('-e')
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
@ -532,17 +535,22 @@ class User(object):
cmd.append('-s')
cmd.append(self.shell)
if self.expires:
current_expires = self.user_password()[1]
if self.expires is not None:
# Convert days since Epoch to seconds since Epoch as struct_time
total_seconds = int(current_expires) * 86400
current_expires = time.gmtime(total_seconds)
current_expires = int(self.user_password()[1])
# Compare year, month, and day only
if current_expires[:3] != self.expires[:3]:
cmd.append('-e')
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
if self.expires < time.gmtime(0):
if current_expires > 0:
cmd.append('-e')
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:
cmd.append('-L')
@ -647,7 +655,7 @@ class User(object):
for line in open(self.SHADOWFILE).readlines():
if line.startswith('%s:' % self.name):
passwd = line.split(':')[1]
expires = line.split(':')[self.SHADOWFILE_EXPIRE_INDEX]
expires = line.split(':')[self.SHADOWFILE_EXPIRE_INDEX] or -1
return passwd, expires
def get_ssh_key_path(self):
@ -845,7 +853,7 @@ class FreeBsdUser(User):
cmd.append('-L')
cmd.append(self.login_class)
if self.expires:
if self.expires is not None:
cmd.append('-e')
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
@ -946,13 +954,22 @@ class FreeBsdUser(User):
new_groups = groups | set(current_groups)
cmd.append(','.join(new_groups))
if self.expires:
current_expires = time.gmtime(int(self.user_password()[1]))
if self.expires is not None:
# Compare year, month, and day only
if current_expires[:3] != self.expires[:3]:
cmd.append('-e')
cmd.append(time.strftime(self.DATE_FORMAT, self.expires))
current_expires = int(self.user_password()[1])
if self.expires < time.gmtime(0):
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
if cmd_len != len(cmd):

View file

@ -246,3 +246,56 @@
- name: Restore original timezone - {{ original_timezone.diff.before.name }}
timezone:
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'