User unexpire (#39758)

* Allow negative values to expires to unexpire a user

Fixes #20096

(cherry picked from commit 34f8080a19)
(cherry picked from commit 54619f70f4)
(cherry picked from commit 8c2fae27d6)
(cherry picked from commit db1a32f8ca)

* tweaked and normalized

 - also added tests, made checking resilient
pull/40291/merge
Brian Coca 7 years ago committed by GitHub
parent 19356c03e8
commit 677fe1076d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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 type: bool
default: "no" default: "no"
login_class:
description:
- Optionally sets the user's login class for FreeBSD, DragonFlyBSD, OpenBSD and
NetBSD systems.
remove: remove:
description: description:
- When used with C(state=absent), behavior is as with C(userdel --remove). - 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:
description:
- Optionally sets the user's login class, a feature of most BSD OSs.
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]))
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)
# Compare year, month, and day only # Current expires is negative or we compare year, month, and day only
if current_expires[:3] != self.expires[:3]: if current_expires <= 0 or current_expire_date[:3] != self.expires[:3]:
cmd.append('-e') cmd.append('-e')
cmd.append(time.strftime(self.DATE_FORMAT, self.expires)) 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…
Cancel
Save