1
0
Fork 0
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:
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. - 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):

View file

@ -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'