xref: /openbmc/webui-vue/src/views/SecurityAndAccess/UserManagement/ModalUser.vue (revision d36ac8a8be8636ddd0e64ce005d507b21bcdeb00)
1<template>
2  <b-modal id="modal-user" ref="modal" :title="modalTitle" @hidden="resetForm">
3    <b-form id="form-user" novalidate @submit.prevent="handleSubmit">
4      <b-container>
5        <!-- Manual unlock form control -->
6        <b-row v-if="!newUser && manualUnlockPolicy && user.Locked">
7          <b-col sm="9">
8            <alert :show="true" variant="warning" small>
9              <template v-if="!v$.form.manualUnlock.$dirty">
10                {{ i18n.t('pageUserManagement.modal.accountLocked') }}
11              </template>
12              <template v-else>
13                {{
14                  i18n.t('pageUserManagement.modal.clickSaveToUnlockAccount')
15                }}
16              </template>
17            </alert>
18          </b-col>
19          <b-col sm="3">
20            <input
21              v-model="form.manualUnlock"
22              data-test-id="userManagement-input-manualUnlock"
23              type="hidden"
24            />
25            <b-button
26              variant="primary"
27              :disabled="v$.form.manualUnlock.$dirty"
28              data-test-id="userManagement-button-manualUnlock"
29              @click="v$.form.manualUnlock.$touch()"
30            >
31              {{ i18n.t('pageUserManagement.modal.unlock') }}
32            </b-button>
33          </b-col>
34        </b-row>
35        <b-row>
36          <b-col>
37            <b-form-group
38              :label="i18n.t('pageUserManagement.modal.accountStatus')"
39            >
40              <b-form-radio
41                v-model="form.status"
42                name="user-status"
43                :value="true"
44                data-test-id="userManagement-radioButton-statusEnabled"
45                @input="v$.form.status.$touch()"
46              >
47                {{ i18n.t('global.status.enabled') }}
48              </b-form-radio>
49              <b-form-radio
50                v-model="form.status"
51                name="user-status"
52                data-test-id="userManagement-radioButton-statusDisabled"
53                :value="false"
54                :disabled="!newUser && originalUsername === disabled"
55                @input="v$.form.status.$touch()"
56              >
57                {{ i18n.t('global.status.disabled') }}
58              </b-form-radio>
59            </b-form-group>
60            <b-form-group
61              :label="i18n.t('pageUserManagement.modal.username')"
62              label-for="username"
63            >
64              <b-form-text id="username-help-block">
65                {{ i18n.t('pageUserManagement.modal.cannotStartWithANumber') }}
66                <br />
67                {{
68                  i18n.t(
69                    'pageUserManagement.modal.noSpecialCharactersExceptUnderscore',
70                  )
71                }}
72              </b-form-text>
73              <b-form-input
74                id="username"
75                v-model="form.username"
76                type="text"
77                aria-describedby="username-help-block"
78                data-test-id="userManagement-input-username"
79                :state="getValidationState(v$.form.username)"
80                :disabled="!newUser && originalUsername === disabled"
81                @input="v$.form.username.$touch()"
82              />
83              <b-form-invalid-feedback role="alert">
84                <template v-if="v$.form.username.required.$invalid">
85                  {{ i18n.t('global.form.fieldRequired') }}
86                </template>
87                <template v-else-if="v$.form.username.maxLength.$invalid">
88                  {{
89                    i18n.t('global.form.lengthMustBeBetween', {
90                      min: 1,
91                      max: 16,
92                    })
93                  }}
94                </template>
95                <template v-else-if="v$.form.username.pattern.$invalid">
96                  {{ i18n.t('global.form.invalidFormat') }}
97                </template>
98              </b-form-invalid-feedback>
99            </b-form-group>
100            <b-form-group
101              :label="i18n.t('pageUserManagement.modal.privilege')"
102              label-for="privilege"
103            >
104              <b-form-select
105                id="privilege"
106                v-model="form.privilege"
107                :options="privilegeTypes"
108                data-test-id="userManagement-select-privilege"
109                :state="getValidationState(v$.form.privilege)"
110                :disabled="!newUser && originalUsername === 'root'"
111                @input="v$.form.privilege.$touch()"
112              >
113                <template #first>
114                  <b-form-select-option :value="null" disabled>
115                    {{ i18n.t('global.form.selectAnOption') }}
116                  </b-form-select-option>
117                </template>
118              </b-form-select>
119              <b-form-invalid-feedback role="alert">
120                <template v-if="v$.form.privilege.required.$invalid">
121                  {{ i18n.t('global.form.fieldRequired') }}
122                </template>
123              </b-form-invalid-feedback>
124            </b-form-group>
125          </b-col>
126          <b-col>
127            <b-form-group
128              :label="i18n.t('pageUserManagement.modal.userPassword')"
129              label-for="password"
130            >
131              <b-form-text id="password-help-block">
132                {{
133                  i18n.t('pageUserManagement.modal.passwordMustBeBetween', {
134                    min: passwordRequirements.minLength,
135                    max: passwordRequirements.maxLength,
136                  })
137                }}
138              </b-form-text>
139              <input-password-toggle>
140                <b-form-input
141                  id="password"
142                  v-model="form.password"
143                  type="password"
144                  autocomplete="new-password"
145                  data-test-id="userManagement-input-password"
146                  aria-describedby="password-help-block"
147                  :state="getValidationState(v$.form.password)"
148                  class="form-control-with-button"
149                  @input="v$.form.password.$touch()"
150                />
151                <b-form-invalid-feedback role="alert">
152                  <template v-if="v$.form.password.required.$invalid">
153                    {{ i18n.t('global.form.fieldRequired') }}
154                  </template>
155                  <template
156                    v-if="
157                      v$.form.password.minLength.$invalid ||
158                      v$.form.password.maxLength.$invalid
159                    "
160                  >
161                    {{
162                      i18n.t('pageUserManagement.modal.passwordMustBeBetween', {
163                        min: passwordRequirements.minLength,
164                        max: passwordRequirements.maxLength,
165                      })
166                    }}
167                  </template>
168                </b-form-invalid-feedback>
169              </input-password-toggle>
170            </b-form-group>
171            <b-form-group
172              :label="i18n.t('pageUserManagement.modal.confirmUserPassword')"
173              label-for="password-confirmation"
174            >
175              <input-password-toggle>
176                <b-form-input
177                  id="password-confirmation"
178                  v-model="form.passwordConfirmation"
179                  data-test-id="userManagement-input-passwordConfirmation"
180                  type="password"
181                  autocomplete="new-password"
182                  :state="getValidationState(v$.form.passwordConfirmation)"
183                  class="form-control-with-button"
184                  @input="v$.form.passwordConfirmation.$touch()"
185                />
186                <b-form-invalid-feedback role="alert">
187                  <template
188                    v-if="v$.form.passwordConfirmation.required.$invalid"
189                  >
190                    {{ i18n.t('global.form.fieldRequired') }}
191                  </template>
192                  <template
193                    v-else-if="
194                      v$.form.passwordConfirmation.sameAsPassword.$invalid
195                    "
196                  >
197                    {{ i18n.t('pageUserManagement.modal.passwordsDoNotMatch') }}
198                  </template>
199                </b-form-invalid-feedback>
200              </input-password-toggle>
201            </b-form-group>
202          </b-col>
203        </b-row>
204      </b-container>
205    </b-form>
206    <template #footer="{ cancel }">
207      <b-button
208        variant="secondary"
209        data-test-id="userManagement-button-cancel"
210        @click="cancel()"
211      >
212        {{ i18n.t('global.action.cancel') }}
213      </b-button>
214      <b-button
215        form="form-user"
216        data-test-id="userManagement-button-submit"
217        type="submit"
218        variant="primary"
219        @click="onOk"
220      >
221        <template v-if="newUser">
222          {{ i18n.t('pageUserManagement.addUser') }}
223        </template>
224        <template v-else>
225          {{ i18n.t('global.action.save') }}
226        </template>
227      </b-button>
228    </template>
229  </b-modal>
230</template>
231
232<script>
233import {
234  required,
235  maxLength,
236  minLength,
237  requiredIf,
238} from '@vuelidate/validators';
239import { helpers, sameAs } from 'vuelidate/lib/validators';
240import VuelidateMixin from '@/components/Mixins/VuelidateMixin.js';
241import { useVuelidate } from '@vuelidate/core';
242
243import InputPasswordToggle from '@/components/Global/InputPasswordToggle';
244import Alert from '@/components/Global/Alert';
245import { useI18n } from 'vue-i18n';
246
247export default {
248  components: { Alert, InputPasswordToggle },
249  mixins: [VuelidateMixin],
250  props: {
251    user: {
252      type: Object,
253      default: null,
254    },
255    passwordRequirements: {
256      type: Object,
257      required: true,
258    },
259  },
260  emits: ['ok', 'hidden'],
261  setup() {
262    const i18n = useI18n();
263    return {
264      v$: useVuelidate(),
265      i18n,
266    };
267  },
268  data() {
269    return {
270      originalUsername: '',
271      form: {
272        status: true,
273        username: '',
274        privilege: null,
275        password: '',
276        passwordConfirmation: '',
277        manualUnlock: false,
278      },
279      disabled: this.$store.getters['global/username'],
280    };
281  },
282  computed: {
283    modalTitle() {
284      return this.newUser
285        ? this.i18n.t('pageUserManagement.addUser')
286        : this.i18n.t('pageUserManagement.editUser');
287    },
288    newUser() {
289      return this.user ? false : true;
290    },
291    accountSettings() {
292      return this.$store.getters['userManagement/accountSettings'];
293    },
294    manualUnlockPolicy() {
295      return !this.accountSettings.accountLockoutDuration;
296    },
297    privilegeTypes() {
298      return this.$store.getters['userManagement/accountRoles'];
299    },
300  },
301  watch: {
302    user: function (value) {
303      if (value === null) return;
304      this.originalUsername = value.username;
305      this.form.username = value.username;
306      this.form.status = value.Enabled;
307      this.form.privilege = value.privilege;
308    },
309  },
310  validations() {
311    return {
312      form: {
313        status: {
314          required,
315        },
316        username: {
317          required,
318          maxLength: maxLength(16),
319          pattern: helpers.regex('pattern', /^([a-zA-Z_][a-zA-Z0-9_]*)/),
320        },
321        privilege: {
322          required,
323        },
324        password: {
325          required: requiredIf(function () {
326            return this.requirePassword();
327          }),
328          minLength: minLength(this.passwordRequirements.minLength),
329          maxLength: maxLength(this.passwordRequirements.maxLength),
330        },
331        passwordConfirmation: {
332          required: requiredIf(function () {
333            return this.requirePassword();
334          }),
335          sameAsPassword: sameAs('password'),
336        },
337        manualUnlock: {},
338      },
339    };
340  },
341  methods: {
342    handleSubmit() {
343      let userData = {};
344
345      if (this.newUser) {
346        this.v$.$touch();
347        if (this.v$.$invalid) return;
348        userData.username = this.form.username;
349        userData.status = this.form.status;
350        userData.privilege = this.form.privilege;
351        userData.password = this.form.password;
352      } else {
353        if (this.v$.$invalid) return;
354        userData.originalUsername = this.originalUsername;
355        if (this.v$.form.status.$dirty) {
356          userData.status = this.form.status;
357        }
358        if (this.v$.form.username.$dirty) {
359          userData.username = this.form.username;
360        }
361        if (this.v$.form.privilege.$dirty) {
362          userData.privilege = this.form.privilege;
363        }
364        if (this.v$.form.password.$dirty) {
365          userData.password = this.form.password;
366        }
367        if (this.v$.form.manualUnlock.$dirty) {
368          // If form manualUnlock control $dirty then
369          // set user Locked property to false
370          userData.locked = false;
371        }
372        if (Object.entries(userData).length === 1) {
373          this.closeModal();
374          return;
375        }
376      }
377
378      this.$emit('ok', { isNewUser: this.newUser, userData });
379      this.closeModal();
380    },
381    closeModal() {
382      this.$nextTick(() => {
383        this.$refs.modal.hide();
384      });
385    },
386    resetForm() {
387      this.form.originalUsername = '';
388      this.form.status = true;
389      this.form.username = '';
390      this.form.privilege = null;
391      this.form.password = '';
392      this.form.passwordConfirmation = '';
393      this.v$.$reset();
394      this.$emit('hidden');
395    },
396    requirePassword() {
397      if (this.newUser) return true;
398      if (this.v$.form.password.$dirty) return true;
399      if (this.v$.form.passwordConfirmation.$dirty) return true;
400      return false;
401    },
402    onOk(bvModalEvt) {
403      // prevent modal close
404      bvModalEvt.preventDefault();
405      this.handleSubmit();
406    },
407  },
408};
409</script>
410