Repository: ansible/ansible-modules-core

Issue:
Feature request: expand functionality of "user" module
##### ISSUE TYPE

Feature Idea
##### COMPONENT NAME

user module
##### ANSIBLE VERSION

N/A
##### SUMMARY

The user module allows us to modify aspects of a user's account, including the password and some controls on the password.  This is apparently done (primarily) through the useradd, usermod, and userdel operating system commands.  The passwd command on most Linux distros and on Solaris (and perhaps other Unix variants) allows use of the -n, -x, and -w options to control password aging.  I would like to see those capabilities added to the user module.  I'm not proficient in Python and even less familiar with the inner workings of Ansible modules, otherwise I would add password aging capabilities myself.  My immediate interest is actually in disabling password aging (passwd -x -1 username, for example) but on some systems I'll need to enable it and set the parameters.



Discussion:
<!-- @rhorer: Thanks for filing this feature idea! -->

@grossws ping, this issue is waiting for your response.
[click here for bot help](https://github.com/jctanner/ansibullbot/blob/ISSUE_TRIAGE_CLASS/ISSUE_HELP.md)

<!--- boilerplate: issue_bug_new --->

The current situation is that the user module on Solaris is doing harm to the shadow file.

When you change a user's password the user module directly edits the shadow file and only fills the first three columns: username,  password hash and last changed. It then replaces the rest of the line with empty ::: fields overwriting existing entries for password aging and failed passwords.

Sorry, the last comment is not exactly true.
Nothing in the shadow file gets forgotten / reset. But because the user modfication does not use "usermod", it does not honor the settings for password aging from /etc/default/passwd:
- MAXWEEKS
- MINWEEKS
- WARNWEEKS

The user module should be able to read the file and set the apropriate values in the shadow file.


Resolving patch:
diff --git a/system/user.py b/system/user.py
old mode 100755
new mode 100644
--- a/system/user.py
+++ b/system/user.py
@@ -1235,6 +1235,29 @@ class SunOS(User):
     distribution = None
     SHADOWFILE = '/etc/shadow'
 
+    def get_password_defaults(self):
+        # Read password aging defaults
+        try:
+            minweeks = ''
+            maxweeks = ''
+            warnweeks = ''
+            for line in open("/etc/default/passwd", 'r'):
+                line = line.strip()
+                if (line.startswith('#') or line == ''):
+                    continue
+                key, value = line.split('=')
+                if key == "MINWEEKS":
+                    minweeks = value.rstrip('\n')
+                elif key == "MAXWEEKS":
+                    maxweeks = value.rstrip('\n')
+                elif key == "WARNWEEKS":
+                    warnweeks = value.rstrip('\n')
+        except Exception:
+            err = get_exception()
+            self.module.fail_json(msg="failed to read /etc/default/passwd: %s" % str(err))
+
+        return (minweeks, maxweeks, warnweeks)
+
     def remove_user(self):
         cmd = [self.module.get_bin_path('userdel', True)]
         if self.remove:
@@ -1292,6 +1315,7 @@ def create_user(self):
         if not self.module.check_mode:
             # we have to set the password by editing the /etc/shadow file
             if self.password is not None:
+                minweeks, maxweeks, warnweeks = self.get_password_defaults()
                 try:
                     lines = []
                     for line in open(self.SHADOWFILE, 'rb').readlines():
@@ -1301,6 +1325,12 @@ def create_user(self):
                             continue
                         fields[1] = self.password
                         fields[2] = str(int(time.time() / 86400))
+                        if minweeks:
+                            fields[3] = str(int(minweeks) * 7)
+                        if maxweeks:
+                            fields[4] = str(int(maxweeks) * 7)
+                        if warnweeks:
+                            fields[5] = str(int(warnweeks) * 7)
                         line = ':'.join(fields)
                         lines.append('%s\n' % line)
                     open(self.SHADOWFILE, 'w+').writelines(lines)
@@ -1379,6 +1409,7 @@ def modify_user_usermod(self):
         if self.update_password == 'always' and self.password is not None and info[1] != self.password:
             (rc, out, err) = (0, '', '')
             if not self.module.check_mode:
+                minweeks, maxweeks, warnweeks = self.get_password_defaults()
                 try:
                     lines = []
                     for line in open(self.SHADOWFILE, 'rb').readlines():
@@ -1388,6 +1419,12 @@ def modify_user_usermod(self):
                             continue
                         fields[1] = self.password
                         fields[2] = str(int(time.time() / 86400))
+                        if minweeks:
+                            fields[3] = str(int(minweeks) * 7)
+                        if maxweeks:
+                            fields[4] = str(int(maxweeks) * 7)
+                        if warnweeks:
+                            fields[5] = str(int(warnweeks) * 7)
                         line = ':'.join(fields)
                         lines.append('%s\n' % line)
                     open(self.SHADOWFILE, 'w+').writelines(lines)


Test patch: