Add password requirements to local user page

- Make api call to get user account settings
- Update add/edit user form to include dynamic password
  requirement values
- Fix edit username bug by adding input listener to field
  that sets form control to $dirty state and adds
  property to PATCH request

Signed-off-by: Yoshie Muranaka <yoshiemuranaka@gmail.com>
Change-Id: I3535f4214ee12c95d5e502134bf3e36597d2421a
diff --git a/src/store/modules/AccessControl/LocalUserMangementStore.js b/src/store/modules/AccessControl/LocalUserMangementStore.js
index eb5822e..67c3a1e 100644
--- a/src/store/modules/AccessControl/LocalUserMangementStore.js
+++ b/src/store/modules/AccessControl/LocalUserMangementStore.js
@@ -19,16 +19,44 @@
 const LocalUserManagementStore = {
   namespaced: true,
   state: {
-    allUsers: []
+    allUsers: [],
+    accountLockoutDuration: null,
+    accountLockoutThreshold: null,
+    accountMinPasswordLength: null,
+    accountMaxPasswordLength: null
   },
   getters: {
     allUsers(state) {
       return state.allUsers;
+    },
+    accountSettings(state) {
+      return {
+        lockoutDuration: state.accountLockoutDuration,
+        lockoutThreshold: state.accountLockoutThreshold
+      };
+    },
+    accountPasswordRequirements(state) {
+      return {
+        minLength: state.accountMinPasswordLength,
+        maxLength: state.accountMaxPasswordLength
+      };
     }
   },
   mutations: {
     setUsers(state, allUsers) {
       state.allUsers = allUsers;
+    },
+    setLockoutDuration(state, lockoutDuration) {
+      state.accountLockoutDuration = lockoutDuration;
+    },
+    setLockoutThreshold(state, lockoutThreshold) {
+      state.accountLockoutThreshold = lockoutThreshold;
+    },
+    setAccountMinPasswordLength(state, minPasswordLength) {
+      state.accountMinPasswordLength = minPasswordLength;
+    },
+    setAccountMaxPasswordLength(state, maxPasswordLength) {
+      state.accountMaxPasswordLength = maxPasswordLength;
     }
   },
   actions: {
@@ -46,6 +74,20 @@
           throw new Error('Error loading local users.');
         });
     },
+    getAccountSettings({ commit }) {
+      api
+        .get('/redfish/v1/AccountService')
+        .then(({ data }) => {
+          commit('setLockoutDuration', data.AccountLockoutDuration);
+          commit('setLockoutThreshold', data.AccountLockoutThreshold);
+          commit('setAccountMinPasswordLength', data.MinPasswordLength);
+          commit('setAccountMaxPasswordLength', data.MaxPasswordLength);
+        })
+        .catch(error => {
+          console.log(error);
+          throw new Error('Error loading account settings.');
+        });
+    },
     async createUser({ dispatch }, { username, password, privilege, status }) {
       const data = {
         UserName: username,
diff --git a/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue b/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue
index 8797da7..97b00e4 100644
--- a/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue
+++ b/src/views/AccessControl/LocalUserManagement/LocalUserManagement.vue
@@ -75,8 +75,12 @@
       </b-col>
     </b-row>
     <!-- Modals -->
-    <modal-settings :settings="settings"></modal-settings>
-    <modal-user :user="activeUser" @ok="saveUser"></modal-user>
+    <modal-settings :settings="settings" />
+    <modal-user
+      :user="activeUser"
+      :password-requirements="passwordRequirements"
+      @ok="saveUser"
+    />
   </b-container>
 </template>
 
@@ -116,7 +120,6 @@
   data() {
     return {
       activeUser: null,
-      settings: null,
       fields: [
         {
           key: 'checkbox',
@@ -174,15 +177,25 @@
           ...user
         };
       });
+    },
+    settings() {
+      return this.$store.getters['localUsers/accountSettings'];
+    },
+    passwordRequirements() {
+      return this.$store.getters['localUsers/accountPasswordRequirements'];
     }
   },
   created() {
     this.getUsers();
+    this.getAccountSettings();
   },
   methods: {
     getUsers() {
       this.$store.dispatch('localUsers/getUsers');
     },
+    getAccountSettings() {
+      this.$store.dispatch('localUsers/getAccountSettings');
+    },
     initModalUser(user) {
       this.activeUser = user;
       this.$bvModal.show('modal-user');
diff --git a/src/views/AccessControl/LocalUserManagement/ModalSettings.vue b/src/views/AccessControl/LocalUserManagement/ModalSettings.vue
index a0d6294..afe2d95 100644
--- a/src/views/AccessControl/LocalUserManagement/ModalSettings.vue
+++ b/src/views/AccessControl/LocalUserManagement/ModalSettings.vue
@@ -6,8 +6,8 @@
 export default {
   props: {
     settings: {
-      type: String,
-      default: ''
+      type: Object,
+      required: true
     }
   }
 };
diff --git a/src/views/AccessControl/LocalUserManagement/ModalUser.vue b/src/views/AccessControl/LocalUserManagement/ModalUser.vue
index 448276b..4a6a0bc 100644
--- a/src/views/AccessControl/LocalUserManagement/ModalUser.vue
+++ b/src/views/AccessControl/LocalUserManagement/ModalUser.vue
@@ -43,6 +43,7 @@
                 aria-describedby="username-help-block"
                 :state="getValidationState($v.form.username)"
                 :disabled="!newUser && originalUsername === 'root'"
+                @input="$v.form.username.$touch()"
               />
               <b-form-invalid-feedback role="alert">
                 <template v-if="!$v.form.username.required">
@@ -73,9 +74,13 @@
           </b-col>
           <b-col>
             <b-form-group label="User password" label-for="password">
-              <b-form-text id="password-help-block" text-variant="black">
-                <!-- TODO: Should be dynamic values -->
-                Password must between 8 – 20 characters
+              <b-form-text id="password-help-block">
+                Password must between
+                <span class="text-nowrap">
+                  {{ passwordRequirements.minLength }}
+                  – {{ passwordRequirements.maxLength }}
+                </span>
+                characters
               </b-form-text>
               <input-password-toggle>
                 <b-form-input
@@ -95,7 +100,12 @@
                       !$v.form.password.minLength || !$v.form.password.maxLength
                     "
                   >
-                    Length must be between 8 – 20 characters
+                    Length must be between
+                    <span class="text-nowrap">
+                      {{ passwordRequirements.minLength }}
+                      – {{ passwordRequirements.maxLength }}
+                    </span>
+                    characters
                   </template>
                 </b-form-invalid-feedback>
               </input-password-toggle>
@@ -158,6 +168,10 @@
     user: {
       type: Object,
       default: null
+    },
+    passwordRequirements: {
+      type: Object,
+      required: true
     }
   },
   data() {
@@ -187,33 +201,35 @@
       this.form.privilege = value.privilege;
     }
   },
-  validations: {
-    form: {
-      status: {
-        required
-      },
-      username: {
-        required,
-        maxLength: maxLength(16),
-        pattern: helpers.regex('pattern', /^([a-zA-Z_][a-zA-Z0-9_]*)/)
-      },
-      privilege: {
-        required
-      },
-      password: {
-        required: requiredIf(function() {
-          return this.requirePassword();
-        }),
-        minLength: minLength(8), //TODO: Update to dynamic backend values
-        maxLength: maxLength(20) //TODO: UPdate to dynamic backend values
-      },
-      passwordConfirmation: {
-        required: requiredIf(function() {
-          return this.requirePassword();
-        }),
-        sameAsPassword: sameAs('password')
+  validations() {
+    return {
+      form: {
+        status: {
+          required
+        },
+        username: {
+          required,
+          maxLength: maxLength(16),
+          pattern: helpers.regex('pattern', /^([a-zA-Z_][a-zA-Z0-9_]*)/)
+        },
+        privilege: {
+          required
+        },
+        password: {
+          required: requiredIf(function() {
+            return this.requirePassword();
+          }),
+          minLength: minLength(this.passwordRequirements.minLength),
+          maxLength: maxLength(this.passwordRequirements.maxLength)
+        },
+        passwordConfirmation: {
+          required: requiredIf(function() {
+            return this.requirePassword();
+          }),
+          sameAsPassword: sameAs('password')
+        }
       }
-    }
+    };
   },
   methods: {
     handleSubmit() {