1/**
2 * Controller for user Accounts
3 *
4 * @module app/access-control
5 * @exports userController
6 * @name userController
7 */
8
9window.angular && (function(angular) {
10  'use strict';
11
12  angular.module('app.accessControl').controller('userController', [
13    '$scope', 'APIUtils', 'toastService', '$uibModal', '$q',
14    function($scope, APIUtils, toastService, $uibModal, $q) {
15      $scope.loading;
16      $scope.accountSettings;
17      $scope.userRoles;
18      $scope.localUsers;
19
20      $scope.tableData = [];
21      $scope.tableHeader = [
22        {label: 'Username'}, {label: 'Privilege'}, {label: 'Account status'}
23      ];
24      $scope.tableBatchActions = [
25        {type: 'delete', label: 'Remove'},
26        {type: 'enable', label: 'Enable'},
27        {type: 'disable', label: 'Disable'},
28      ];
29
30      /**
31       * Returns true if username is 'root'
32       * @param {*} user
33       */
34      function checkIfRoot(user) {
35        return user.UserName === 'root' ? true : false;
36      }
37
38      /**
39       * Data table mapper
40       * @param {*} user
41       * @returns user
42       */
43      function mapTableData(user) {
44        const accountStatus = user.Locked ? 'Locked' :
45            user.Enabled                  ? 'Enabled' :
46                                            'Disabled';
47        const editAction = {type: 'Edit', enabled: true, file: 'icon-edit.svg'};
48        const deleteAction = {
49          type: 'Delete',
50          enabled: checkIfRoot(user) ? false : true,
51          file: 'icon-trashcan.svg'
52        };
53        user.selectable = checkIfRoot(user) ? false : true;
54        user.actions = [editAction, deleteAction];
55        user.uiData = [user.UserName, user.RoleId, accountStatus];
56        return user;
57      }
58
59      /**
60       * Returns lockout method based on the lockout duration property
61       * If the lockoutDuration is greater than 0 the lockout method
62       * is automatic otherwise the lockout method is manual
63       * @param {number} lockoutDuration
64       * @returns {number} : returns the account lockout method
65       *                     1(automatic) / 0(manual)
66       */
67      function mapLockoutMethod(lockoutDuration) {
68        return lockoutDuration > 0 ? 1 : 0;
69      }
70
71      /**
72       * API call to get all user accounts
73       */
74      function getLocalUsers() {
75        $scope.loading = true;
76        APIUtils.getAllUserAccounts()
77            .then((users) => {
78              $scope.localUsers = users;
79              $scope.tableData = users.map(mapTableData);
80            })
81            .catch((error) => {
82              console.log(JSON.stringify(error));
83              toastService.error('Failed to load users.');
84            })
85            .finally(() => {
86              $scope.loading = false;
87            })
88      }
89
90      /**
91       * API call to get current Account settings
92       */
93      function getAccountSettings() {
94        APIUtils.getAllUserAccountProperties()
95            .then((settings) => {
96              $scope.accountSettings = settings;
97            })
98            .catch((error) => {
99              console.log(JSON.stringify(error));
100              $scope.accountSettings = null;
101            })
102      }
103
104      /**
105       * API call to get local user roles
106       */
107      function getUserRoles() {
108        APIUtils.getAccountServiceRoles()
109            .then((roles) => {
110              $scope.userRoles = roles;
111            })
112            .catch((error) => {
113              console.log(JSON.stringify(error));
114              $scope.userRoles = null;
115            })
116      }
117
118      /**
119       * API call to create new user
120       * @param {*} user
121       */
122      function createUser(username, password, role, enabled) {
123        $scope.loading = true;
124        APIUtils.createUser(username, password, role, enabled)
125            .then(() => {
126              getLocalUsers();
127              toastService.success(`User '${username}' has been created.`);
128            })
129            .catch((error) => {
130              console.log(JSON.stringify(error));
131              toastService.error(`Failed to create new user '${username}'.`);
132            })
133            .finally(() => {
134              $scope.loading = false;
135            });
136      }
137
138      /**
139       * API call to update existing user
140       */
141      function updateUser(
142          originalUsername, username, password, role, enabled, locked) {
143        $scope.loading = true;
144        APIUtils
145            .updateUser(
146                originalUsername, username, password, role, enabled, locked)
147            .then(() => {
148              getLocalUsers();
149              toastService.success('User has been updated successfully.')
150            })
151            .catch((error) => {
152              console.log(JSON.stringify(error));
153              toastService.error(`Unable to update user '${originalUsername}'.`)
154            })
155            .finally(() => {
156              $scope.loading = false;
157            })
158      }
159
160      /**
161       * API call to delete users
162       * @param {*} users : Array of users to delete
163       */
164      function deleteUsers(users = []) {
165        $scope.loading = true;
166        const promises =
167            users.map((user) => APIUtils.deleteUser(user.UserName));
168        $q.all(promises)
169            .then(() => {
170              let message;
171              if (users.length > 1) {
172                message = 'Users have been removed.'
173              } else {
174                message = `User '${users[0].UserName}' has been removed.`
175              }
176              toastService.success(message);
177            })
178            .catch((error) => {
179              console.log(JSON.stringify(error));
180              let message;
181              if (users.length > 1) {
182                message = 'Failed to remove users.'
183              } else {
184                message = `Failed to remove user '${users[0].UserName}'.`
185              }
186              toastService.error(message);
187            })
188            .finally(() => {
189              getLocalUsers();
190              $scope.loading = false;
191            });
192      }
193
194      /**
195       * API call to update user status enabled/disabled
196       * @param {*} users : Array of users to update
197       * @param {boolean} enabled : status
198       */
199      function updateUserStatus(users = [], enabled = true) {
200        $scope.loading = true;
201        const promises = users.map(
202            (user) => APIUtils.updateUser(
203                user.UserName, null, null, null, enabled, null));
204        $q.all(promises)
205            .then(() => {
206              let message;
207              let statusLabel = enabled ? 'enabled' : 'disabled';
208              if (users.length > 1) {
209                message = `Users ${statusLabel}.`
210              } else {
211                message = `User '${users[0].UserName}' ${statusLabel}.`;
212              }
213              toastService.success(message);
214            })
215            .catch((error) => {
216              console.log(JSON.stringify(error));
217              let message;
218              let statusLabel = enabled ? 'enable' : 'disable';
219              if (users.length > 1) {
220                message = `Failed to ${statusLabel} users.`
221              } else {
222                message =
223                    `Failed to ${statusLabel} user '${users[0].UserName}'.`
224              }
225              toastService.error(message);
226            })
227            .finally(() => {
228              getLocalUsers();
229              $scope.loading = false;
230            });
231      }
232
233      /**
234       * API call to save account policy settings
235       * @param {number} lockoutDuration
236       * @param {number} lockoutThreshold
237       */
238      function updateAccountSettings(lockoutDuration, lockoutThreshold) {
239        $scope.loading = true;
240        APIUtils.saveUserAccountProperties(lockoutDuration, lockoutThreshold)
241            .then(() => {
242              $scope.accountSettings['AccountLockoutDuration'] =
243                  lockoutDuration;
244              $scope.accountSettings['AccountLockoutThreshold'] =
245                  lockoutThreshold;
246              toastService.success(
247                  'Account policy settings have been updated.');
248            })
249            .catch((error) => {
250              console.log(JSON.stringify(error));
251              toastService.error('Failed to update account policy settings.');
252            })
253            .finally(() => {
254              $scope.loading = false;
255            });
256      }
257
258      /**
259       * Initiate account settings modal
260       */
261      function initAccountSettingsModal() {
262        const template = require('./user-accounts-modal-settings.html');
263        $uibModal
264            .open({
265              template,
266              windowTopClass: 'uib-modal',
267              ariaLabelledBy: 'dialog_label',
268              controllerAs: 'modalCtrl',
269              controller: function() {
270                const lockoutMethod = mapLockoutMethod(
271                    $scope.accountSettings.AccountLockoutDuration);
272
273                this.settings = {};
274                this.settings.maxLogin =
275                    $scope.accountSettings.AccountLockoutThreshold;
276                this.settings.lockoutMethod = lockoutMethod;
277                this.settings.timeoutDuration = !lockoutMethod ?
278                    null :
279                    $scope.accountSettings.AccountLockoutDuration;
280              }
281            })
282            .result
283            .then((form) => {
284              if (form.$valid) {
285                const lockoutDuration = form.lockoutMethod.$modelValue ?
286                    form.timeoutDuration.$modelValue :
287                    0;
288                const lockoutThreshold = form.maxLogin.$modelValue;
289                updateAccountSettings(lockoutDuration, lockoutThreshold);
290              }
291            })
292            .catch(
293                () => {
294                    // do nothing
295                })
296      }
297
298      /**
299       * Initiate user modal
300       * Can be triggered by clicking edit in table or 'Add user' button
301       * If triggered from the table, user parameter will be provided
302       * If triggered by add user button, user parameter will be undefined
303       * @optional @param {*} user
304       */
305      function initUserModal(user) {
306        if ($scope.userRoles === null || $scope.userRoles === undefined) {
307          // If userRoles failed to load,  do not allow add/edit
308          // functionality
309          return;
310        }
311        const newUser = user ? false : true;
312        const originalUsername = user ? angular.copy(user.UserName) : null;
313        const template = require('./user-accounts-modal-user.html');
314        $uibModal
315            .open({
316              template,
317              windowTopClass: 'uib-modal',
318              ariaLabelledBy: 'dialog_label',
319              controllerAs: 'modalCtrl',
320              controller: function() {
321                // Set default status to Enabled
322                const status = newUser ? true : user.Enabled;
323                // Check if UserName is root
324                // Some form controls will be disabled for root users:
325                // edit enabled status, edit username, edit role
326                const isRoot = newUser ? false :
327                    checkIfRoot(user)  ? true :
328                                         false;
329                // Array of existing usernames (excluding current user instance)
330                const existingUsernames =
331                    $scope.localUsers.reduce((acc, val) => {
332                      if (user && (val.UserName === user.UserName)) {
333                        return acc;
334                      }
335                      acc.push(val.UserName);
336                      return acc;
337                    }, []);
338
339                this.user = {};
340                this.user.isRoot = isRoot;
341                this.user.new = newUser;
342                this.user.accountStatus = status;
343                this.user.username = newUser ? '' : user.UserName;
344                this.user.privilege = newUser ? '' : user.RoleId;
345                this.user.locked = newUser ? null : user.Locked;
346
347                this.manualUnlockProperty = false;
348                this.automaticLockout = mapLockoutMethod(
349                    $scope.accountSettings.AccountLockoutDuration);
350                this.privilegeRoles = $scope.userRoles;
351                this.existingUsernames = existingUsernames;
352                this.minPasswordLength = $scope.accountSettings ?
353                    $scope.accountSettings.MinPasswordLength :
354                    null;
355                this.maxPasswordLength = $scope.accountSettings ?
356                    $scope.accountSettings.MaxPasswordLength :
357                    null;
358              }
359            })
360            .result
361            .then((form) => {
362              if (form.$valid) {
363                // If form control is pristine set property to null
364                // this will make sure only changed values are updated when
365                // modifying existing users
366                // API utils checks for null values
367                const username =
368                    form.username.$pristine ? null : form.username.$modelValue;
369                const password =
370                    form.password.$pristine ? null : form.password.$modelValue;
371                const role = form.privilege.$pristine ?
372                    null :
373                    form.privilege.$modelValue;
374                const enabled = (form.accountStatus.$pristine &&
375                                 form.accountStatus1.$pristine) ?
376                    null :
377                    form.accountStatus.$modelValue;
378                const locked = (form.lock && form.lock.$dirty) ?
379                    form.lock.$modelValue :
380                    null;
381
382                if (!newUser) {
383                  updateUser(
384                      originalUsername, username, password, role, enabled,
385                      locked);
386                } else {
387                  createUser(
388                      username, password, role, form.accountStatus.$modelValue);
389                }
390              }
391            })
392            .catch(
393                () => {
394                    // do nothing
395                })
396      }
397
398      /**
399       * Intiate remove users modal
400       * @param {*} users
401       */
402      function initRemoveModal(users) {
403        const template = require('./user-accounts-modal-remove.html');
404        $uibModal
405            .open({
406              template,
407              windowTopClass: 'uib-modal',
408              ariaLabelledBy: 'dialog_label',
409              controllerAs: 'modalCtrl',
410              controller: function() {
411                this.users = users;
412              }
413            })
414            .result
415            .then(() => {
416              deleteUsers(users);
417            })
418            .catch(
419                () => {
420                    // do nothing
421                })
422      }
423
424      /**
425       * Callback when action emitted from table
426       * @param {*} value
427       */
428      $scope.onEmitRowAction = (value) => {
429        switch (value.action) {
430          case 'Edit':
431            initUserModal(value.row);
432            break;
433          case 'Delete':
434            initRemoveModal([value.row]);
435            break;
436          default:
437        }
438      };
439
440      /**
441       * Callback when batch action emitted from table
442       */
443      $scope.onEmitBatchAction = (value) => {
444        switch (value.action) {
445          case 'delete':
446            initRemoveModal(value.filteredRows);
447            break;
448          case 'enable':
449            updateUserStatus(value.filteredRows, true)
450            break;
451          case 'disable':
452            updateUserStatus(value.filteredRows, false)
453            break;
454          default:
455            break;
456        }
457      };
458
459      /**
460       * Callback when 'Account settings policy' button clicked
461       */
462      $scope.onClickAccountSettingsPolicy = () => {
463        initAccountSettingsModal();
464      };
465
466      /**
467       * Callback when 'Add user' button clicked
468       */
469      $scope.onClickAddUser = () => {
470        initUserModal();
471      };
472
473      /**
474       * Callback when controller view initially loaded
475       */
476      $scope.$on('$viewContentLoaded', () => {
477        getLocalUsers();
478        getUserRoles();
479        getAccountSettings();
480      })
481    }
482  ]);
483})(angular);
484