Repository: ansible/ansible
Pull request: Add umask option to user module
##### SUMMARY
Fixes: #40359

##### ISSUE TYPE
- Feature Pull Request

##### COMPONENT NAME
user

##### ADDITIONAL INFORMATION
This has been opened previously on https://github.com/ansible/ansible/pull/71469.

Problem statement:
Option to set UMASK with USER module. 
<!---
Verify first that your issue/request is not already reported on GitHub.
THIS FORM WILL BE READ BY A MACHINE, COMPLETE ALL SECTIONS AS DESCRIBED.
Also test if the latest release, and devel branch are affected too.
ALWAYS add information AFTER (OUTSIDE) these html comments.
Otherwise it may end up being automatically closed by our bot. -->

##### ISSUE TYPE
 - Feature Idea

##### COMPONENT NAME
<!--- Insert, BELOW THIS COMMENT, the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path-->
user

##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "ansible --version" between quotes below -->
```
[root@awx ~]# ansible --version
ansible 2.6.0 (devel 6a4f3fb729) last updated 2018/05/17 15:18:06 (GMT -400)
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /root/ansible/lib/ansible
  executable location = /root/ansible/bin/ansible
  python version = 2.7.5 (default, Apr 11 2018, 07:36:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]
[root@awx ~]# 

```

##### SUMMARY
<!--- Explain the problem briefly -->
Module parameter to set UMASK when creating a user. It looks like this would only work if the `local` parameter for the module is left at default (`local: no`) so that the useradd command is being run which has a `-K` opiton for UMASK setting. 

```
       -K, --key KEY=VALUE
           Overrides /etc/login.defs defaults (UID_MIN, UID_MAX, UMASK, PASS_MAX_DAYS and others).

           Example: -K PASS_MAX_DAYS=-1 can be used when creating system account to turn off password ageing, even though system account has
           no password at all. Multiple -K options can be specified, e.g.: -K UID_MIN=100-K UID_MAX=499
```

Hints from issue discussion:
Files identified in the description:
* [lib/ansible/modules/system/user.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/system/user.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @sfromm
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
cc @samdoran
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Files identified in the description:
* [`lib/ansible/modules/user.py`](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/user.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->

Commit message: Remove created users in integration test at last

Patch to review:
diff --git a/changelogs/fragments/73821-user-add_umask_option.yaml b/changelogs/fragments/73821-user-add_umask_option.yaml
new file mode 100644
index 00000000000000..49380cecc02522
--- /dev/null
+++ b/changelogs/fragments/73821-user-add_umask_option.yaml
@@ -0,0 +1,3 @@
+---
+minor_changes:
+  - user - Add umask option (https://github.com/ansible/ansible/issues/40359).
diff --git a/lib/ansible/modules/user.py b/lib/ansible/modules/user.py
index 5e43d051917158..18d1d15f9cdf9f 100644
--- a/lib/ansible/modules/user.py
+++ b/lib/ansible/modules/user.py
@@ -250,6 +250,14 @@
             - Supported on Linux only.
         type: int
         version_added: "2.11"
+    umask:
+        description:
+            - Sets the umask of the user.
+            - Does nothing when used with other platforms.
+            - Currently supported on Linux.
+            - Does not work on Fedora versions >= 32.
+        type: str
+        version_added: "2.11"
 
 notes:
   - There are specific requirements per platform on user management utilities. However
@@ -529,6 +537,7 @@ def __init__(self, module):
         self.role = module.params['role']
         self.password_expire_max = module.params['password_expire_max']
         self.password_expire_min = module.params['password_expire_min']
+        self.umask = module.params['umask']
 
         if module.params['groups'] is not None:
             self.groups = ','.join(module.params['groups'])
@@ -708,6 +717,10 @@ def create_user_useradd(self):
             if self.skeleton is not None:
                 cmd.append('-k')
                 cmd.append(self.skeleton)
+
+            if self.umask is not None:
+                cmd.append('-K')
+                cmd.append('UMASK=' + self.umask)
         else:
             cmd.append('-M')
 
@@ -1357,6 +1370,10 @@ def create_user(self):
                 cmd.append('-k')
                 cmd.append(self.skeleton)
 
+            if self.umask is not None:
+                cmd.append('-K')
+                cmd.append('UMASK=' + self.umask)
+
         if self.shell is not None:
             cmd.append('-s')
             cmd.append(self.shell)
@@ -1434,6 +1451,10 @@ def modify_user(self):
                 cmd.append('-k')
                 cmd.append(self.skeleton)
 
+            if self.umask is not None:
+                cmd.append('-K')
+                cmd.append('UMASK=' + self.umask)
+
         if self.group is not None:
             if not self.group_exists(self.group):
                 self.module.fail_json(msg="Group %s does not exist" % self.group)
@@ -1613,6 +1634,10 @@ def create_user(self):
                 cmd.append('-k')
                 cmd.append(self.skeleton)
 
+            if self.umask is not None:
+                cmd.append('-K')
+                cmd.append('UMASK=' + self.umask)
+
         cmd.append(self.name)
         return self.execute_command(cmd)
 
@@ -1786,6 +1811,10 @@ def create_user(self):
                 cmd.append('-k')
                 cmd.append(self.skeleton)
 
+            if self.umask is not None:
+                cmd.append('-K')
+                cmd.append('UMASK=' + self.umask)
+
         cmd.append(self.name)
         return self.execute_command(cmd)
 
@@ -1971,6 +2000,10 @@ def create_user(self):
                 cmd.append('-k')
                 cmd.append(self.skeleton)
 
+            if self.umask is not None:
+                cmd.append('-K')
+                cmd.append('UMASK=' + self.umask)
+
         if self.profile is not None:
             cmd.append('-P')
             cmd.append(self.profile)
@@ -2576,6 +2609,10 @@ def create_user_useradd(self, command_name='useradd'):
                 cmd.append('-k')
                 cmd.append(self.skeleton)
 
+            if self.umask is not None:
+                cmd.append('-K')
+                cmd.append('UMASK=' + self.umask)
+
         cmd.append(self.name)
         (rc, out, err) = self.execute_command(cmd)
 
@@ -2901,6 +2938,10 @@ def create_user(self):
             cmd.append('-k')
             cmd.append(self.skeleton)
 
+        if self.umask is not None:
+            cmd.append('-K')
+            cmd.append('UMASK=' + self.umask)
+
         if self.system:
             cmd.append('-S')
 
@@ -3046,6 +3087,7 @@ def main():
             profile=dict(type='str'),
             authorization=dict(type='str'),
             role=dict(type='str'),
+            umask=dict(type='str'),
         ),
         supports_check_mode=True,
     )
diff --git a/test/integration/targets/user/tasks/main.yml b/test/integration/targets/user/tasks/main.yml
index d3bae05644fc52..e76ef3b601d00a 100644
--- a/test/integration/targets/user/tasks/main.yml
+++ b/test/integration/targets/user/tasks/main.yml
@@ -37,3 +37,5 @@
 - import_tasks: test_password_lock.yml
 - import_tasks: test_password_lock_new_user.yml
 - import_tasks: test_local.yml
+- import_tasks: test_umask.yml
+  when: ansible_facts.system == 'Linux' and not (ansible_distribution == 'Fedora' and ansible_distribution_major_version is version('32', '>='))
diff --git a/test/integration/targets/user/tasks/test_umask.yml b/test/integration/targets/user/tasks/test_umask.yml
new file mode 100644
index 00000000000000..7048e95f1f954c
--- /dev/null
+++ b/test/integration/targets/user/tasks/test_umask.yml
@@ -0,0 +1,38 @@
+---
+- name: Create user with 000 umask
+  user:
+    name: umaskuser_test_1
+    umask: "000"
+  register: umaskuser_test_1
+
+- name: Create user with 077 umask
+  user:
+    name: umaskuser_test_2
+    umask: "077"
+  register: umaskuser_test_2
+
+- name: check permissions on created home folder
+  stat:
+    path: "{{ user_home_prefix[ansible_facts.system] }}/umaskuser_test_1"
+  register: umaskuser_test_1_path
+
+- name: check permissions on created home folder
+  stat:
+    path: "{{ user_home_prefix[ansible_facts.system] }}/umaskuser_test_2"
+  register: umaskuser_test_2_path
+
+- name: remove created users
+  user:
+    name: "{{ item }}"
+    state: absent
+  register: umaskuser_test_remove
+  loop:
+    - umaskuser_test_1
+    - umaskuser_test_2
+
+- name: Ensure correct umask has been set on created users
+  assert:
+    that:
+      - umaskuser_test_1_path.stat.mode == "0777"
+      - umaskuser_test_2_path.stat.mode == "0700"
+      - umaskuser_test_remove is changed


Code review comments:
diff_hunk: @@ -250,6 +250,14 @@
             - Supported on Linux only.
         type: int
         version_added: "2.11"
+    umask:
+        description:
+            - Sets the umask of the user.
+            - Does nothing when used with other platforms.
+            - Currently supported on Linux.
+            - Does not work on Fedora versions >= 32.
+        type: str
+        version_added: "2.11"
path: lib/ansible/modules/user.py
text: @user1:
```suggestion
        version_added: "2.12"
```