# Test code for the user module.
# (c) 2017, James Tanner <tanner.jc@gmail.com>

# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
#

## user add

- name: remove the test user
  user:
    name: ansibulluser
    state: absent

- name: try to create a user
  user:
    name: ansibulluser
    state: present
  register: user_test0_0

- name: create the user again
  user:
    name: ansibulluser
    state: present
  register: user_test0_1

- debug:
    var: user_test0
    verbosity: 2

- name: make a list of users
  script: userlist.sh {{ ansible_facts.distribution }}
  register: user_names

- debug:
    var: user_names
    verbosity: 2

- name: validate results for testcase 0
  assert:
    that:
      - user_test0_0 is changed
      - user_test0_1 is not changed
      - '"ansibulluser" in user_names.stdout_lines'

# create system user

- name: remove user
  user:
    name: ansibulluser
    state: absent

- name: create system user
  user:
    name: ansibulluser
    state: present
    system: yes

# test adding user with uid
# https://github.com/ansible/ansible/issues/62969
- name: remove the test user
  user:
    name: ansibulluser
    state: absent

- name: try to create a user with uid
  user:
    name: ansibulluser
    state: present
    uid: 572
  register: user_test01_0

- name: create the user again
  user:
    name: ansibulluser
    state: present
    uid: 572
  register: user_test01_1

- name: validate results for testcase 0
  assert:
    that:
      - user_test01_0 is changed
      - user_test01_1 is not changed

# test user add with password
- name: add an encrypted password for user
  user:
    name: ansibulluser
    password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."
    state: present
    update_password: always
  register: test_user_encrypt0

- name: there should not be warnings
  assert:
    that: "'warnings' not in test_user_encrypt0"

# https://github.com/ansible/ansible/issues/65711
- name: Test updating password only on creation
  user:
    name: ansibulluser
    password: '*'
    update_password: on_create
  register: test_user_update_password

- name: Ensure password was not changed
  assert:
    that:
      - test_user_update_password is not changed

- name: Verify password hash for Linux
  when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']
  block:
    - name: LINUX | Get shadow entry for ansibulluser
      getent:
        database: shadow
        key: ansibulluser

    - name: LINUX | Ensure password hash was not removed
      assert:
        that:
          - getent_shadow['ansibulluser'][1] != '*'

- block:
    - name: add an plaintext password for user
      user:
        name: ansibulluser
        password: "plaintextpassword"
        state: present
        update_password: always
      register: test_user_encrypt1

    - name: there should be a warning complains that the password is plaintext
      assert:
        that: "'warnings' in test_user_encrypt1"

    - name: add an invalid hashed password
      user:
        name: ansibulluser
        password: "$6$rounds=656000$tgK3gYTyRLUmhyv2$lAFrYUQwn7E6VsjPOwQwoSx30lmpiU9r/E0Al7tzKrR9mkodcMEZGe9OXD0H/clOn6qdsUnaL4zefy5fG+++++"
        state: present
        update_password: always
      register: test_user_encrypt2

    - name: there should be a warning complains about the character set of password
      assert:
        that: "'warnings' in test_user_encrypt2"

    - name: change password to '!'
      user:
        name: ansibulluser
        password: '!'
      register: test_user_encrypt3

    - name: change password to '*'
      user:
        name: ansibulluser
        password: '*'
      register: test_user_encrypt4

    - name: change password to '*************'
      user:
        name: ansibulluser
        password: '*************'
      register: test_user_encrypt5

    - name: there should be no warnings when setting the password to '!',  '*' or '*************'
      assert:
        that:
          - "'warnings' not in test_user_encrypt3"
          - "'warnings' not in test_user_encrypt4"
          - "'warnings' not in test_user_encrypt5"
  when: ansible_facts.system != 'Darwin'


# https://github.com/ansible/ansible/issues/42484
# Skipping macOS for now since there is a bug when changing home directory
- block:
    - name: create user specifying home
      user:
        name: ansibulluser
        state: present
        home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
      register: user_test3_0

    - name: create user again specifying home
      user:
        name: ansibulluser
        state: present
        home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
      register: user_test3_1

    - name: change user home
      user:
        name: ansibulluser
        state: present
        home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser-mod"
      register: user_test3_2

    - name: change user home back
      user:
        name: ansibulluser
        state: present
        home: "{{ user_home_prefix[ansible_facts.system] }}/ansibulluser"
      register: user_test3_3

    - name: validate results for testcase 3
      assert:
        that:
          - user_test3_0 is not changed
          - user_test3_1 is not changed
          - user_test3_2 is changed
          - user_test3_3 is changed
  when: ansible_facts.system != 'Darwin'

# https://github.com/ansible/ansible/issues/41393
# Create a new user account with a path that has parent directories that do not exist
- name: Create user with home path that has parents that do not exist
  user:
    name: ansibulluser2
    state: present
    home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
  register: create_home_with_no_parent_1

- name: Create user with home path that has parents that do not exist again
  user:
    name: ansibulluser2
    state: present
    home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
  register: create_home_with_no_parent_2

- name: Check the created home directory
  stat:
    path: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
  register: home_with_no_parent_3

- name: Ensure user with non-existing parent paths was created successfully
  assert:
    that:
      - create_home_with_no_parent_1 is changed
      - create_home_with_no_parent_1.home == user_home_prefix[ansible_facts.system] ~ '/in2deep/ansibulluser2'
      - create_home_with_no_parent_2 is not changed
      - home_with_no_parent_3.stat.uid == create_home_with_no_parent_1.uid
      - home_with_no_parent_3.stat.gr_name == default_user_group[ansible_facts.distribution] | default('ansibulluser2')

- name: Cleanup test account
  user:
    name: ansibulluser2
    home: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/ansibulluser2"
    state: absent
    remove: yes

- name: Remove testing dir
  file:
    path: "{{ user_home_prefix[ansible_facts.system] }}/in2deep/"
    state: absent


# https://github.com/ansible/ansible/issues/60307
# Make sure we can create a user when the home directory is missing
- name: Create user with home path that does not exist
  user:
    name: ansibulluser3
    state: present
    home: "{{ user_home_prefix[ansible_facts.system] }}/nosuchdir"
    createhome: no

- name: Cleanup test account
  user:
    name: ansibulluser3
    state: absent
    remove: yes

# https://github.com/ansible/ansible/issues/70589
# Create user with create_home: no and parent directory does not exist.
- name: "Check if parent dir for home dir for user exists (before)"
  stat:
    path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir"
  register: create_user_no_create_home_with_no_parent_parent_dir_before

- name: "Create user with create_home == no and home path parent dir does not exist"
  user:
    name: randomuser
    state: present
    create_home: false
    home: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir/randomuser"
  register: create_user_no_create_home_with_no_parent

- name: "Check if parent dir for home dir for user exists (after)"
  stat:
    path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir"
  register: create_user_no_create_home_with_no_parent_parent_dir_after

- name: "Check if home for user is created"
  stat:
    path: "{{ user_home_prefix[ansible_facts.system] }}/thereisnodir/randomuser"
  register: create_user_no_create_home_with_no_parent_home_dir

- name: "Ensure user with non-existing parent paths with create_home: no was created successfully"
  assert:
    that:
      - not create_user_no_create_home_with_no_parent_parent_dir_before.stat.exists
      - not create_user_no_create_home_with_no_parent_parent_dir_after.stat.isdir is defined
      - not create_user_no_create_home_with_no_parent_home_dir.stat.exists

- name: Cleanup test account
  user:
    name: randomuser
    state: absent
    remove: yes

## user check

- name: run existing user check tests
  user:
    name: "{{ user_names.stdout_lines | random }}"
    state: present
    create_home: no
  loop: "{{ range(1, 5+1) | list }}"
  register: user_test1

- debug:
    var: user_test1
    verbosity: 2

- name: validate results for testcase 1
  assert:
    that:
      - user_test1.results is defined
      - user_test1.results | length == 5

- name: validate changed results for testcase 1
  assert:
    that:
      - "user_test1.results[0] is not changed"
      - "user_test1.results[1] is not changed"
      - "user_test1.results[2] is not changed"
      - "user_test1.results[3] is not changed"
      - "user_test1.results[4] is not changed"
      - "user_test1.results[0]['state'] == 'present'"
      - "user_test1.results[1]['state'] == 'present'"
      - "user_test1.results[2]['state'] == 'present'"
      - "user_test1.results[3]['state'] == 'present'"
      - "user_test1.results[4]['state'] == 'present'"


## user remove

- name: try to delete the user
  user:
    name: ansibulluser
    state: absent
    force: true
  register: user_test2

- name: make a new list of users
  script: userlist.sh {{ ansible_facts.distribution }}
  register: user_names2

- debug:
    var: user_names2
    verbosity: 2

- name: validate results for testcase 2
  assert:
    that:
      - '"ansibulluser" not in user_names2.stdout_lines'


## create user without home and test fallback home dir create

- block:
    - name: create the user
      user:
        name: ansibulluser

    - name: delete the user and home dir
      user:
        name: ansibulluser
        state: absent
        force: true
        remove: true

    - name: create the user without home
      user:
        name: ansibulluser
        create_home: no

    - name: create the user home dir
      user:
        name: ansibulluser
      register: user_create_home_fallback

    - name: stat home dir
      stat:
        path: '{{ user_create_home_fallback.home }}'
      register: user_create_home_fallback_dir

    - name: read UMASK from /etc/login.defs and return mode
      shell: |
        import re
        import os
        try:
            for line in open('/etc/login.defs').readlines():
                m = re.match(r'^UMASK\s+(\d+)$', line)
                if m:
                    umask = int(m.group(1), 8)
        except:
            umask = os.umask(0)
        mode = oct(0o777 & ~umask)
        print(str(mode).replace('o', ''))
      args:
        executable: "{{ ansible_python_interpreter }}"
      register: user_login_defs_umask

    - name: validate that user home dir is created
      assert:
        that:
          - user_create_home_fallback is changed
          - user_create_home_fallback_dir.stat.exists
          - user_create_home_fallback_dir.stat.isdir
          - user_create_home_fallback_dir.stat.pw_name == 'ansibulluser'
          - user_create_home_fallback_dir.stat.mode == user_login_defs_umask.stdout
  when: ansible_facts.system != 'Darwin'

- block:
    - name: create non-system user on macOS to test the shell is set to /bin/bash
      user:
        name: macosuser
      register: macosuser_output

    - name: validate the shell is set to /bin/bash
      assert:
        that:
          - 'macosuser_output.shell == "/bin/bash"'

    - name: cleanup
      user:
        name: macosuser
        state: absent

    - name: create system user on macos to test the shell is set to /usr/bin/false
      user:
        name: macosuser
        system: yes
      register: macosuser_output

    - name: validate the shell is set to /usr/bin/false
      assert:
        that:
          - 'macosuser_output.shell == "/usr/bin/false"'

    - name: cleanup
      user:
        name: macosuser
        state: absent

    - name: create non-system user on macos and set the shell to /bin/sh
      user:
        name: macosuser
        shell: /bin/sh
      register: macosuser_output

    - name: validate the shell is set to /bin/sh
      assert:
        that:
          - 'macosuser_output.shell == "/bin/sh"'

    - name: cleanup
      user:
        name: macosuser
        state: absent
  when: ansible_facts.distribution == "MacOSX"


## user expires
# Date is March 3, 2050
- name: Set user expiration
  user:
    name: ansibulluser
    state: present
    expires: 2529881062
  register: user_test_expires1
  tags:
    - timezone

- name: Set user expiration again to ensure no change is made
  user:
    name: ansibulluser
    state: present
    expires: 2529881062
  register: user_test_expires2
  tags:
    - timezone

- name: Ensure that account with expiration was created and did not change on subsequent run
  assert:
    that:
      - user_test_expires1 is changed
      - user_test_expires2 is not changed

- name: Verify 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:
        that:
          - getent_shadow['ansibulluser'][6] == '29281'
  when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']


- name: Verify 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:
        that:
          - bsd_account_expiration.stdout == '2529881062'
  when: ansible_facts.os_family == 'FreeBSD'

- name: Change timezone
  timezone:
    name: America/Denver
  register: original_timezone
  tags:
    - timezone

- name: Change system timezone to make sure expiration comparison works properly
  block:
    - name: Create user with expiration again to ensure no change is made in a new timezone
      user:
        name: ansibulluser
        state: present
        expires: 2529881062
      register: user_test_different_tz
      tags:
        - timezone

    - name: Ensure that no change was reported
      assert:
        that:
          - user_test_different_tz is not changed
      tags:
        - timezone

  always:
    - name: Restore original timezone - {{ original_timezone.diff.before.name }}
      timezone:
        name: "{{ original_timezone.diff.before.name }}"
      when: original_timezone.diff.before.name != "n/a"
      tags:
        - timezone

    - name: Restore original timezone when n/a
      file:
        path: /etc/sysconfig/clock
        state: absent
      when:
        - original_timezone.diff.before.name == "n/a"
        - "'/etc/sysconfig/clock' in original_timezone.msg"
      tags:
        - timezone


- 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] | int < 0
  when: ansible_facts.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_facts.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_facts.os_family == 'FreeBSD'

# Test setting no expiration when creating a new account
# https://github.com/ansible/ansible/issues/44155
- name: Remove ansibulluser
  user:
    name: ansibulluser
    state: absent

- name: Create user account without expiration
  user:
    name: ansibulluser
    state: present
    expires: -1
  register: user_test_create_no_expires_1

- name: Create user account without expiration again
  user:
    name: ansibulluser
    state: present
    expires: -1
  register: user_test_create_no_expires_2

- name: Ensure changes were made appropriately
  assert:
    msg: Setting 'expires='-1 resulted in incorrect changes
    that:
      - user_test_create_no_expires_1 is changed
      - user_test_create_no_expires_2 is not changed

- 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] | int < 0
  when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']

- 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_facts.os_family == 'FreeBSD'

# Test setting epoch 0 expiration when creating a new account, then removing the expiry
# https://github.com/ansible/ansible/issues/47114
- name: Remove ansibulluser
  user:
    name: ansibulluser
    state: absent

- name: Create user account with epoch 0 expiration
  user:
    name: ansibulluser
    state: present
    expires: 0
  register: user_test_expires_create0_1

- name: Create user account with epoch 0 expiration again
  user:
    name: ansibulluser
    state: present
    expires: 0
  register: user_test_expires_create0_2

- name: Change the user account to remove the expiry time
  user:
    name: ansibulluser
    expires: -1
  register: user_test_remove_expires_1

- name: Change the user account to remove the expiry time again
  user:
    name: ansibulluser
    expires: -1
  register: user_test_remove_expires_2


- name: Verify un expiration date for Linux
  block:
    - name: LINUX | Ensure changes were made appropriately
      assert:
        msg: Creating an account with 'expries=0' then removing that expriation with 'expires=-1' resulted in incorrect changes
        that:
          - user_test_expires_create0_1 is changed
          - user_test_expires_create0_2 is not changed
          - user_test_remove_expires_1 is changed
          - user_test_remove_expires_2 is not changed

    - 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] | int < 0
  when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']


- name: Verify proper expiration behavior for BSD
  block:
    - name: BSD | Ensure changes were made appropriately
      assert:
        msg: Creating an account with 'expries=0' then removing that expriation with 'expires=-1' resulted in incorrect changes
        that:
          - user_test_expires_create0_1 is changed
          - user_test_expires_create0_2 is not changed
          - user_test_remove_expires_1 is not changed
          - user_test_remove_expires_2 is not changed
  when: ansible_facts.os_family == 'FreeBSD'

# Test expiration with a very large negative number. This should have the same
# result as setting -1.
- name: Set expiration date using very long negative number
  user:
    name: ansibulluser
    state: present
    expires: -2529881062
  register: user_test_expires5

- name: Ensure no change was made
  assert:
    that:
      - user_test_expires5 is not changed

- 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] | int < 0
  when: ansible_facts.os_family in ['RedHat', 'Debian', 'Suse']

- 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_facts.os_family == 'FreeBSD'


## shadow backup
- block:
    - name: Create a user to test shadow file backup
      user:
        name: ansibulluser
        state: present
      register: result

    - name: Find shadow backup files
      find:
        path: /etc
        patterns: 'shadow\..*~$'
        use_regex: yes
      register: shadow_backups

    - name: Assert that a backup file was created
      assert:
        that:
          - result.bakup
          - shadow_backups.files | map(attribute='path') | list | length > 0
  when: ansible_facts.os_family == 'Solaris'


# Test creating ssh key with passphrase
- name: Remove ansibulluser
  user:
    name: ansibulluser
    state: absent

- name: Create user with ssh key
  user:
    name: ansibulluser
    state: present
    generate_ssh_key: yes
    force: yes
    ssh_key_file: "{{ output_dir }}/test_id_rsa"
    ssh_key_passphrase: secret_passphrase

- name: Unlock ssh key
  command: "ssh-keygen -y -f {{ output_dir }}/test_id_rsa -P secret_passphrase"
  register: result

- name: Check that ssh key was unlocked successfully
  assert:
    that:
      - result.rc == 0

- name: Clean ssh key
  file:
    path: "{{ output_dir }}/test_id_rsa"
    state: absent
  when: ansible_os_family == 'FreeBSD'


## password lock
- block:
    - name: Set password for ansibulluser
      user:
        name: ansibulluser
        password: "$6$rounds=656000$TT4O7jz2M57npccl$33LF6FcUMSW11qrESXL1HX0BS.bsiT6aenFLLiVpsQh6hDtI9pJh5iY7x8J7ePkN4fP8hmElidHXaeD51pbGS."

    - name: Lock account
      user:
        name: ansibulluser
        password_lock: yes
      register: password_lock_1

    - name: Lock account again
      user:
        name: ansibulluser
        password_lock: yes
      register: password_lock_2

    - name: Unlock account
      user:
        name: ansibulluser
        password_lock: no
      register: password_lock_3

    - name: Unlock account again
      user:
        name: ansibulluser
        password_lock: no
      register: password_lock_4

    - name: Ensure task reported changes appropriately
      assert:
        msg: The password_lock tasks did not make changes appropriately
        that:
          - password_lock_1 is changed
          - password_lock_2 is not changed
          - password_lock_3 is changed
          - password_lock_4 is not changed

    - name: Lock account
      user:
        name: ansibulluser
        password_lock: yes

    - name: Verify account lock for BSD
      block:
        - name: BSD | Get account status
          shell: "{{ status_command[ansible_facts['system']] }}"
          register: account_status_locked

        - name: Unlock account
          user:
            name: ansibulluser
            password_lock: no

        - name: BSD | Get account status
          shell: "{{ status_command[ansible_facts['system']] }}"
          register: account_status_unlocked

        - name: FreeBSD | Ensure account is locked
          assert:
            that:
              - "'LOCKED' in account_status_locked.stdout"
              - "'LOCKED' not in account_status_unlocked.stdout"
          when: ansible_facts['system'] == 'FreeBSD'

      when: ansible_facts['system'] in ['FreeBSD', 'OpenBSD']

    - name: Verify account lock for Linux
      block:
        - name: LINUX | Get account status
          getent:
            database: shadow
            key: ansibulluser

        - name: LINUX | Ensure account is locked
          assert:
            that:
              - getent_shadow['ansibulluser'][0].startswith('!')

        - name: Unlock account
          user:
            name: ansibulluser
            password_lock: no

        - name: LINUX | Get account status
          getent:
            database: shadow
            key: ansibulluser

        - name: LINUX | Ensure account is unlocked
          assert:
            that:
              - not getent_shadow['ansibulluser'][0].startswith('!')

      when: ansible_facts['system'] == 'Linux'

  always:
    - name: Unlock account
      user:
        name: ansibulluser
        password_lock: no

  when: ansible_facts['system'] in ['FreeBSD', 'OpenBSD', 'Linux']


  ## Check local mode
  # Even if we don't have a system that is bound to a directory, it's useful
  # to run with local: true to exercise the code path that reads through the local
  # user database file.
  # https://github.com/ansible/ansible/issues/50947

- name: Create /etc/gshadow
  file:
    path: /etc/gshadow
    state: touch
  when: ansible_facts.os_family == 'Suse'
  tags:
    - user_test_local_mode

- name: Create /etc/libuser.conf
  file:
    path: /etc/libuser.conf
    state: touch
  when:
    - ansible_facts.distribution == 'Ubuntu'
    - ansible_facts.distribution_major_version is version_compare('16', '==')
  tags:
    - user_test_local_mode

- name: Ensure luseradd is present
  action: "{{ ansible_facts.pkg_mgr }}"
  args:
    name: libuser
    state: present
  when: ansible_facts.system in ['Linux']
  tags:
    - user_test_local_mode

- name: Create local account that already exists to check for warning
  user:
    name: root
    local: yes
  register: local_existing
  tags:
    - user_test_local_mode

- name: Create local_ansibulluser
  user:
    name: local_ansibulluser
    state: present
    local: yes
  register: local_user_test_1
  tags:
    - user_test_local_mode

- name: Create local_ansibulluser again
  user:
    name: local_ansibulluser
    state: present
    local: yes
  register: local_user_test_2
  tags:
    - user_test_local_mode

- name: Remove local_ansibulluser
  user:
    name: local_ansibulluser
    state: absent
    remove: yes
    local: yes
  register: local_user_test_remove_1
  tags:
    - user_test_local_mode

- name: Remove local_ansibulluser again
  user:
    name: local_ansibulluser
    state: absent
    remove: yes
    local: yes
  register: local_user_test_remove_2
  tags:
    - user_test_local_mode

- name: Create test groups
  group:
    name: "{{ item }}"
  loop:
    - testgroup1
    - testgroup2
    - testgroup3
    - testgroup4
  tags:
    - user_test_local_mode

- name: Create local_ansibulluser with groups
  user:
    name: local_ansibulluser
    state: present
    local: yes
    groups: ['testgroup1', 'testgroup2']
  register: local_user_test_3
  ignore_errors: yes
  tags:
    - user_test_local_mode

- name: Append groups for local_ansibulluser
  user:
    name: local_ansibulluser
    state: present
    local: yes
    groups: ['testgroup3', 'testgroup4']
    append: yes
  register: local_user_test_4
  ignore_errors: yes
  tags:
    - user_test_local_mode

- name: Test append without groups for local_ansibulluser
  user:
    name: local_ansibulluser
    state: present
    append: yes
  register: local_user_test_5
  ignore_errors: yes
  tags:
    - user_test_local_mode

- name: Remove local_ansibulluser again
  user:
    name: local_ansibulluser
    state: absent
    remove: yes
    local: yes
  tags:
    - user_test_local_mode

- name: Remove test groups
  group:
    name: "{{ item }}"
    state: absent
  loop:
    - testgroup1
    - testgroup2
    - testgroup3
    - testgroup4
  tags:
    - user_test_local_mode

- name: Ensure local user accounts were created and removed properly
  assert:
    that:
      - local_user_test_1 is changed
      - local_user_test_2 is not changed
      - local_user_test_3 is changed
      - local_user_test_4 is changed
      - local_user_test_remove_1 is changed
      - local_user_test_remove_2 is not changed
  tags:
    - user_test_local_mode

- name: Ensure warnings were displayed properly
  assert:
    that:
      - local_user_test_1['warnings'] | length > 0
      - local_user_test_1['warnings'] | first is search('The local user account may already exist')
      - local_user_test_5['warnings'] is search("'append' is set, but no 'groups' are specified. Use 'groups'")
      - local_existing['warnings'] is not defined
  when: ansible_facts.system in ['Linux']
  tags:
    - user_test_local_mode