ソースを参照

Merge remote-tracking branch 'joswhite/master' into ng-helpdesk

jalbr74 7 年 前
コミット
650ba695b9

+ 8 - 8
client/src/helpdesk/password-suggestions-dialog.html → client/src/changepassword/autogen-change-password.component.html

@@ -20,34 +20,34 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog class="password-suggestions-dialog">
+<ias-dialog class="autogen-change-password-dialog">
     <div class="ias-dialog-header">
         <div class="ias-title" ng-bind="'Title_RandomPasswords' | translate"></div>
     </div>
-
     <div class="ias-dialog-body">
         <p ng-bind="'Display_PasswordGeneration' | translate"></p>
         <table>
             <tbody>
             <tr ng-repeat="i in [0,2,4,6,8,10,12,14,16,18]">
                 <td ng-repeat="j in [i, i+1]">
-                    <div href="#" ng-bind="$ctrl.passwordSuggestions[j]" ng-click="$ctrl.onChoosePassword(j)">
+                    <div ng-bind="$ctrl.passwordSuggestions[j]" ng-click="$ctrl.onChoosePasswordSuggestion(j)">
                     </div>
                 </td>
             </tr>
             </tbody>
         </table>
     </div>
-
     <div class="ias-actions">
-        <mf-button ng-click="$ctrl.onMoreRandomsButtonClick()"
-                   ng-disabled="$ctrl.fetchingRandoms">{{ 'Button_More' | translate }}</mf-button>
-        <mf-button ng-click="close()">{{ 'Button_Cancel' | translate }}</mf-button>
+        <mf-button ng-click="$ctrl.populatePasswordSuggestions()"
+                   ng-disabled="$ctrl.fetchingRandoms">{{ 'Button_More' | translate }}
+        </mf-button>
+        <mf-button ng-click="cancel()">{{ 'Button_Cancel' | translate }}</mf-button>
     </div>
+
     <mf-icon-button class="ias-dialog-close-button"
                     icon="close_thick"
                     id="close-icon"
                     ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
-                    ng-click="close()">
+                    ng-click="cancel()">
     </mf-icon-button>
 </ias-dialog>

+ 37 - 0
client/src/changepassword/autogen-change-password.component.scss

@@ -0,0 +1,37 @@
+/*!
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+ias-dialog {
+  &.autogen-change-password-dialog {
+    table {
+      td {
+        min-width: 160px;
+        height: 15px;
+
+        div {
+          cursor: pointer;
+          font-family: monospace;
+        }
+      }
+    }
+  }
+}

+ 35 - 29
client/src/helpdesk/password-suggestions.controller.ts → client/src/changepassword/autogen-change-password.controller.ts

@@ -21,47 +21,31 @@
  */
 
 
-import {IHelpDeskService, IRandomPasswordResponse} from '../services/helpdesk.service';
+import {IHelpDeskService, IRandomPasswordResponse, ISuccessResponse} from '../services/helpdesk.service';
 import {IPromise, IQService} from 'angular';
+import DialogService from '../ux/ias-dialog.service';
+import {IChangePasswordSuccess} from './success-change-password.controller';
 
-let RANDOM_MAPPING_LENGTH = 20;
+const RANDOM_MAPPING_SIZE = 20;
 
-require('helpdesk/password-suggestions-dialog.scss');
+require('changepassword/autogen-change-password.component.scss');
 
-export default class PasswordSuggestionsDialogController {
+export default class AutogenChangePasswordController {
     fetchingRandoms: boolean;
     passwordSuggestions: string[];
 
-    static $inject = [ '$q', 'HelpDeskService', 'personUserKey' ];
-    constructor(private $q: IQService, private HelpDeskService: IHelpDeskService, private personUserKey: string) {
+    static $inject = [ '$q', 'HelpDeskService', 'IasDialogService', 'personUserKey' ];
+    constructor(private $q: IQService,
+                private HelpDeskService: IHelpDeskService,
+                private IasDialogService: DialogService,
+                private personUserKey: string) {
         this.passwordSuggestions = Array(20).fill('');
-        this.fetchRandoms();
-    }
-
-    onChoosePassword(index: number) {
-        let password = this.passwordSuggestions[index];
-        // change password
-    }
-
-    onMoreRandomsButtonClick() {
-        this.fetchRandoms();
-    }
-
-    fetchRandoms() {
-        this.fetchingRandoms = true;
-        let ordering = this.generateRandomMapping();
-        let promiseChain: IPromise<any> = this.$q.when();
-        ordering.forEach((index: number) => {
-            promiseChain = promiseChain.then(this.passwordSuggestionFactory(index));
-        });
-        promiseChain.then(() => {
-            this.fetchingRandoms = false;
-        });
+        this.populatePasswordSuggestions();
     }
 
     generateRandomMapping(): number[] {
         let map: number[] = [];
-        for (let i = 0; i < RANDOM_MAPPING_LENGTH; i++) {
+        for (let i = 0; i < RANDOM_MAPPING_SIZE; i++) {
             map.push(i);
         }
         let randomComparatorFunction = () => 0.5 - Math.random();
@@ -70,6 +54,16 @@ export default class PasswordSuggestionsDialogController {
         return map;
     }
 
+    onChoosePasswordSuggestion(index: number) {
+        let chosenPassword = this.passwordSuggestions[index];
+        this.HelpDeskService.setPassword(this.personUserKey, false, chosenPassword)
+            .then((result: ISuccessResponse) => {
+                // Send the password and success message to the parent element via the close() method.
+                let data: IChangePasswordSuccess = { password: chosenPassword, successMessage: result.successMessage };
+                this.IasDialogService.close(data);
+            });
+    }
+
     passwordSuggestionFactory(index: number): any {
         return () => {
             return this.HelpDeskService.getRandomPassword(this.personUserKey).then(
@@ -79,4 +73,16 @@ export default class PasswordSuggestionsDialogController {
             );
         };
     }
+
+    populatePasswordSuggestions() {
+        this.fetchingRandoms = true;
+        let ordering = this.generateRandomMapping();
+        let promiseChain: IPromise<any> = this.$q.when();
+        ordering.forEach((index: number) => {
+            promiseChain = promiseChain.then(this.passwordSuggestionFactory(index));
+        });
+        promiseChain.then(() => {
+            this.fetchingRandoms = false;
+        });
+    }
 }

+ 41 - 0
client/src/changepassword/random-change-password.component.html

@@ -0,0 +1,41 @@
+<!--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2017 The PWM Project
+  ~
+  ~ This program is free software; you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation; either version 2 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program; if not, write to the Free Software
+  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  -->
+
+<ias-dialog class="random-change-password-dialog">
+    <div class="ias-dialog-header">
+        <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ': ' + $ctrl.personUsername"></div>
+    </div>
+    <div class="ias-dialog-body">
+        <p ng-bind="'Display_SetRandomPasswordPrompt' | translate"></p>
+    </div>
+    <div class="ias-actions">
+        <mf-button ng-click="$ctrl.confirmSetRandomPassword()">{{ 'Button_OK' | translate }}</mf-button>
+        <mf-button ng-click="cancel()">{{ 'Button_Cancel' | translate }}</mf-button>
+    </div>
+
+    <mf-icon-button class="ias-dialog-close-button"
+                    icon="close_thick"
+                    id="close-icon"
+                    ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
+                    ng-click="cancel()">
+    </mf-icon-button>
+</ias-dialog>

+ 53 - 0
client/src/changepassword/random-change-password.controller.ts

@@ -0,0 +1,53 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+import {IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
+import DialogService from '../ux/ias-dialog.service';
+import {IChangePasswordSuccess} from './success-change-password.controller';
+
+export default class RandomChangePasswordController {
+
+    static $inject = [
+        'HelpDeskService',
+        'IasDialogService',
+        'personUsername',
+        'personUserKey',
+        'translateFilter'
+    ];
+    constructor(private HelpDeskService: IHelpDeskService,
+                private IasDialogService: DialogService,
+                private personUsername: string,
+                private personUserKey: string,
+                private translateFilter: (id: string) => string) {
+    }
+
+    confirmSetRandomPassword() {
+        this.HelpDeskService.setPassword(this.personUserKey, true)
+            .then((result: ISuccessResponse) => {
+                // Send the password and success message to the parent element via the close() method.
+                let chosenPassword = '[' + this.translateFilter('Display_Random') +  ']';
+                let data: IChangePasswordSuccess = { password: chosenPassword, successMessage: result.successMessage };
+                this.IasDialogService.close(data);
+            });
+    }
+}

+ 48 - 0
client/src/changepassword/success-change-password.component.html

@@ -0,0 +1,48 @@
+<!--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2017 The PWM Project
+  ~
+  ~ This program is free software; you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation; either version 2 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program; if not, write to the Free Software
+  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  -->
+
+<ias-dialog class="success-change-password-dialog">
+    <div class="ias-dialog-header">
+        <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ' - ' + $ctrl.personUsername"></div>
+    </div>
+    <div class="ias-dialog-body">
+        <p ng-bind="$ctrl.successMessage"></p>
+        <span ng-bind="'Field_NewPassword' | translate"></span>
+        <mf-button ng-click="$ctrl.togglePasswordMasked()" ng-if="$ctrl.maskPasswords">
+            {{ 'Button_Show' | translate }}
+        </mf-button>
+        <input ng-model="$ctrl.password" ng-hide="$ctrl.passwordMasked" readonly type="text">
+    </div>
+    <div class="ias-actions">
+        <mf-button ng-click="cancel()">{{ 'Button_OK' | translate }}</mf-button>
+        <mf-button ng-click="$ctrl.clearAnswers()"
+                   ng-if="$ctrl.clearResponsesSetting==='ask'">{{ 'Button_ClearResponses' | translate }}
+        </mf-button>
+    </div>
+
+    <mf-icon-button class="ias-dialog-close-button"
+                    icon="close_thick"
+                    id="close-icon"
+                    ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
+                    ng-click="cancel()">
+    </mf-icon-button>
+</ias-dialog>

+ 80 - 0
client/src/changepassword/success-change-password.controller.ts

@@ -0,0 +1,80 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+import {IHelpDeskService } from '../services/helpdesk.service';
+import {IQService} from 'angular';
+import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
+import DialogService from '../ux/ias-dialog.service';
+
+export interface IChangePasswordSuccess {
+    password: string;
+    successMessage: string;
+}
+
+export default class SuccessChangePasswordController {
+    clearResponsesSetting: string;
+    maskPasswords: boolean;
+    password: string;
+    passwordMasked: boolean;
+    successMessage: string;
+
+    static $inject = [
+        '$q',
+        'changePasswordSuccessData',
+        'ConfigService',
+        'HelpDeskService',
+        'IasDialogService',
+        'personUsername',
+        'personUserKey',
+        'translateFilter'
+    ];
+    constructor(private $q: IQService,
+                changePasswordSuccessData: IChangePasswordSuccess,
+                private configService: IHelpDeskConfigService,
+                private HelpDeskService: IHelpDeskService,
+                private IasDialogService: DialogService,
+                private personUsername: string,
+                private personUserKey: string,
+                private translateFilter: (id: string) => string) {
+        this.password = changePasswordSuccessData.password;
+        this.successMessage = changePasswordSuccessData.successMessage;
+
+        let promise = this.$q.all([
+            this.configService.getClearResponsesSetting(),
+            this.configService.maskPasswordsEnabled()
+        ]);
+        promise.then((result) => {
+            this.clearResponsesSetting = result[0];
+            this.maskPasswords = result[1];
+            this.passwordMasked = this.maskPasswords;
+        });
+    }
+
+    clearAnswers() {
+        this.IasDialogService.close();
+    }
+
+    togglePasswordMasked() {
+        this.passwordMasked = !this.passwordMasked;
+    }
+}

+ 86 - 0
client/src/changepassword/type-change-password.component.html

@@ -0,0 +1,86 @@
+<!--
+  ~ Password Management Servlets (PWM)
+  ~ http://www.pwm-project.org
+  ~
+  ~ Copyright (c) 2006-2009 Novell, Inc.
+  ~ Copyright (c) 2009-2017 The PWM Project
+  ~
+  ~ This program is free software; you can redistribute it and/or modify
+  ~ it under the terms of the GNU General Public License as published by
+  ~ the Free Software Foundation; either version 2 of the License, or
+  ~ (at your option) any later version.
+  ~
+  ~ This program is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  ~ GNU General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU General Public License
+  ~ along with this program; if not, write to the Free Software
+  ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+  -->
+
+<ias-dialog class="type-change-password-dialog">
+    <div class="ias-dialog-header">
+        <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ' - ' + $ctrl.personUsername"></div>
+    </div>
+    <div class="ias-dialog-body">
+        <p ng-bind="$ctrl.message"></p>
+
+        <table>
+        <tbody>
+            <tr>
+                <td>
+                    <input ng-model="$ctrl.password1" ng-hide="$ctrl.password1Masked" type="text">
+                    <input ng-model="$ctrl.password1" ng-show="$ctrl.password1Masked" type="password">
+                    <mf-icon-button
+                        icon="password_thin"
+                        id="password-icon1"
+                        ng-attr-title="{{ 'Button_Show' | translate }}"
+                        ng-click="$ctrl.togglePassword1Masked()"
+                        ng-if="$ctrl.maskPasswords"
+                        ng-show="!!$ctrl.password1"></mf-icon-button>
+                </td>
+                <td>
+                    <div ng-if="$ctrl.showStrengthMeter" ng-show="!!$ctrl.password1">
+                        <div>Strength: <span class="strength-label" ng-bind="$ctrl.strength"></span>
+                        </div>
+                        <div style="height:10px; width:80px; background-color:red; border:1px solid black;"></div>
+                    </div>
+                </td>
+            </tr>
+            <tr>
+                <td>
+                    <input ng-model="$ctrl.password2" ng-hide="$ctrl.password2Masked" type="text">
+                    <input ng-model="$ctrl.password2" ng-show="$ctrl.password2Masked" type="password">
+                    <mf-icon-button
+                        icon="password_thin"
+                        id="password-icon2"
+                        ng-attr-title="{{ 'Button_Show' | translate }}"
+                        ng-click="$ctrl.togglePassword2Masked()"
+                        ng-if="$ctrl.maskPasswords"
+                        ng-show="!!$ctrl.password2"></mf-icon-button>
+                </td>
+                <td>
+                    <span>check</span>
+                    <span>X</span>
+                </td>
+            </tr>
+        </tbody>
+        </table>
+    </div>
+    <div class="ias-actions">
+        <mf-button ng-click="$ctrl.chooseTypedPassword()"
+                   ng-disabled="!$ctrl.passwordAcceptable">{{ 'Button_ChangePassword' | translate }}</mf-button>
+        <mf-button ng-click="$ctrl.status = 'autogen'"
+                   ng-if="$ctrl.passwordUiMode === 'BOTH'">{{ 'Title_RandomPasswords' | translate }}
+        </mf-button>
+    </div>
+
+    <mf-icon-button class="ias-dialog-close-button"
+                    icon="close_thick"
+                    id="close-icon"
+                    ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
+                    ng-click="cancel()">
+    </mf-icon-button>
+</ias-dialog>

+ 53 - 0
client/src/changepassword/type-change-password.component.scss

@@ -0,0 +1,53 @@
+/*!
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+ias-dialog {
+  &.type-change-password-dialog {
+    .ias-dialog-body {
+      table {
+        input, mf-icon-button {
+          vertical-align: middle
+        }
+
+        input {
+          margin: 7px;
+        }
+
+        mf-icon-button {
+          display: inline-block;
+
+          mf-icon {
+            font-size: 20px;
+          }
+        }
+
+        td {
+          min-width: 250px;
+        }
+
+        .strength-label {
+          font-weight: bold;
+        }
+      }
+    }
+  }
+}

+ 116 - 0
client/src/changepassword/type-change-password.controller.ts

@@ -0,0 +1,116 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2017 The PWM Project
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+
+import {IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
+import {IQService, IScope} from 'angular';
+import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
+import DialogService from '../ux/ias-dialog.service';
+import {IChangePasswordSuccess} from './success-change-password.controller';
+
+require('changepassword/type-change-password.component.scss');
+
+export default class TypeChangePasswordController {
+    passwordAcceptable: boolean;
+    maskPasswords: boolean;
+    message: string;
+    password1: string;
+    password2: string;
+    password1Masked: boolean;
+    password2Masked: boolean;
+    passwordUiMode: string;
+    passwordSuggestions: string[];
+    showStrengthMeter: boolean;
+    strength: string = 'Very Strong';
+
+    static $inject = [
+        '$q',
+        '$scope',
+        'ConfigService',
+        'HelpDeskService',
+        'IasDialogService',
+        'personUsername',
+        'personUserKey',
+        'translateFilter'
+    ];
+    constructor(private $q: IQService,
+                private $scope: IScope,
+                private configService: IHelpDeskConfigService,
+                private HelpDeskService: IHelpDeskService,
+                private IasDialogService: DialogService,
+                private personUsername: string,
+                private personUserKey: string,
+                private translateFilter: (id: string) => string) {
+        this.passwordAcceptable = true;
+        this.passwordSuggestions = Array(20).fill('');
+        this.message = translateFilter('Display_PasswordPrompt');
+        this.showStrengthMeter = HelpDeskService.showStrengthMeter;
+
+        let promise = this.$q.all([
+            this.configService.getPasswordUiMode(),
+            this.configService.maskPasswordsEnabled()
+        ]);
+        promise.then((result) => {
+            this.passwordUiMode = result[0];
+            this.maskPasswords = result[1];
+            this.password1Masked = this.maskPasswords;
+            this.password2Masked = this.maskPasswords;
+        });
+
+        // update display (TODO)
+        this.$scope.$watch('$ctrl.password1', (newValue, oldValue) => {
+            if (newValue !== oldValue) {
+                // update display (TODO; first or second?)
+
+                if (this.password2.length) {
+                    this.password2 = '';        // TODO: should we do this.$scope.applyAsync?
+                }
+            }
+        });
+    }
+
+    chooseTypedPassword() {
+        if (!this.passwordAcceptable) {
+            return;
+        }
+
+        this.HelpDeskService.setPassword(this.personUserKey, false, this.password1)
+            .then((result: ISuccessResponse) => {
+                // Send the password and success message to the parent element via the close() method.
+                let data: IChangePasswordSuccess = { password: this.password1, successMessage: result.successMessage };
+                this.IasDialogService.close(data);
+            });
+    }
+
+    // Use the autogenPasswords property to signify to the parent element that the operator clicked "Random Passwords"
+    onClickRandomPasswords() {
+        this.IasDialogService.close({ autogenPasswords: true });
+    }
+
+    togglePassword1Masked() {
+        this.password1Masked = !this.password1Masked;
+    }
+
+    togglePassword2Masked() {
+        this.password2Masked = !this.password2Masked;
+    }
+}

+ 108 - 14
client/src/helpdesk/helpdesk-detail.component.ts

@@ -24,14 +24,18 @@
 import {Component} from '../component';
 import {IButtonInfo, IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
 import {IScope, ui} from '@types/angular';
-import {IActionButtons, IHelpDeskConfigService} from '../services/helpdesk-config.service';
+import {IQService, noop} from 'angular';
+import {IHelpDeskConfigService, PASSWORD_UI_MODES} from '../services/helpdesk-config.service';
 import DialogService from '../ux/ias-dialog.service';
 import {IPeopleService} from '../services/people.service';
 import {IPerson} from '../models/person.model';
-import IasDialogComponent from '../ux/ias-dialog.component';
+import {IChangePasswordSuccess} from '../changepassword/success-change-password.controller';
 
+let autogenChangePasswordTemplateUrl = require('changepassword/autogen-change-password.component.html');
 let helpdeskDetailDialogTemplateUrl = require('./helpdesk-detail-dialog.template.html');
-let passwordSuggestionsDialogTemplateUrl = require('./password-suggestions-dialog.html');
+let randomChangePasswordTemplateUrl = require('changepassword/random-change-password.component.html');
+let successChangePasswordTemplateUrl = require('changepassword/success-change-password.component.html');
+let typeChangePasswordTemplateUrl = require('changepassword/type-change-password.component.html');
 let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html');
 
 const STATUS_WAIT = 'wait';
@@ -43,12 +47,12 @@ const STATUS_SUCCESS = 'success';
     templateUrl: require('helpdesk/helpdesk-detail.component.html')
 })
 export default class HelpDeskDetailComponent {
-    actionButtons: IActionButtons;
     person: any;
     personCard: IPerson;
     photosEnabled: boolean;
 
     static $inject = [
+        '$q',
         '$state',
         '$stateParams',
         'ConfigService',
@@ -56,7 +60,8 @@ export default class HelpDeskDetailComponent {
         'IasDialogService',
         'PeopleService'
     ];
-    constructor(private $state: ui.IStateService,
+    constructor(private $q: IQService,
+                private $state: ui.IStateService,
                 private $stateParams: ui.IStateParamsService,
                 private configService: IHelpDeskConfigService,
                 private helpDeskService: IHelpDeskService,
@@ -85,16 +90,111 @@ export default class HelpDeskDetailComponent {
     }
 
     changePassword(): void {
+        this.configService.getPasswordUiMode()
+            .then((passwordUiMode) => {
+                if (passwordUiMode === PASSWORD_UI_MODES.AUTOGEN) {
+                    this.changePasswordAutogen();
+                }
+                else if (passwordUiMode === PASSWORD_UI_MODES.RANDOM) {
+                    this.changePasswordRandom();
+                }
+                else if (passwordUiMode === PASSWORD_UI_MODES.BOTH || passwordUiMode === PASSWORD_UI_MODES.TYPE) {
+                    this.changePasswordType();
+                }
+                else {
+                    throw new Error('Password type unsupported!');  // TODO: best way to do this?
+                }
+            });
+    }
+
+    changePasswordAutogen() {
         this.IasDialogService
             .open({
-                controller: 'PasswordSuggestionsDialogController as $ctrl',
-                templateUrl: passwordSuggestionsDialogTemplateUrl,
+                controller: 'AutogenChangePasswordController as $ctrl',
+                templateUrl: autogenChangePasswordTemplateUrl,
                 locals: {
-                    personUserKey: this.getUserKey(),
+                    personUserKey: this.getUserKey()
                 }
+            })
+            // If the password was changed, the promise resolves. IasDialogService passes the data intact.
+            .then(this.changePasswordSuccess.bind(this), noop);
+    }
+
+    changePasswordClearResponses() {
+        let userKey = this.getUserKey();
+
+        this.IasDialogService
+            .open({
+                controller: [
+                    '$scope',
+                    'HelpDeskService',
+                    'translateFilter',
+                    function ($scope: IScope,
+                              helpDeskService: IHelpDeskService,
+                              translateFilter: (id: string) => string) {
+                        $scope.status = STATUS_WAIT;
+                        $scope.title = translateFilter('Button_ClearResponses');
+                        helpDeskService.clearResponses(userKey).then((data: ISuccessResponse) => {
+                            // TODO - error dialog?
+                            $scope.status = STATUS_SUCCESS;
+                            $scope.text = data.successMessage;
+                        });
+                    }
+                ],
+                templateUrl: helpdeskDetailDialogTemplateUrl
             });
     }
 
+    changePasswordRandom() {
+        this.IasDialogService
+            .open({
+                controller: 'RandomChangePasswordController as $ctrl',
+                templateUrl: randomChangePasswordTemplateUrl,
+                locals: {
+                    personUsername: this.person.userDisplayName,
+                    personUserKey: this.getUserKey()
+                }
+            })
+            // If the password was changed, the promise resolves. IasDialogService passes the data intact.
+            .then(this.changePasswordSuccess.bind(this), noop);
+    }
+
+    changePasswordSuccess(data: IChangePasswordSuccess) {
+        this.IasDialogService
+            .open({
+                controller: 'SuccessChangePasswordController as $ctrl',
+                templateUrl: successChangePasswordTemplateUrl,
+                locals: {
+                    changePasswordSuccessData: data,
+                    personUsername: this.person.userDisplayName,
+                    personUserKey: this.getUserKey()
+                }
+            })
+            .then(this.changePasswordClearResponses.bind(this), noop);
+    }
+
+    changePasswordType() {
+        this.IasDialogService
+            .open({
+                controller: 'TypeChangePasswordController as $ctrl',
+                templateUrl: typeChangePasswordTemplateUrl,
+                locals: {
+                    personUsername: this.person.userDisplayName,
+                    personUserKey: this.getUserKey()
+                }
+            })          // TODO: right data type?
+            // If the operator clicked "Random Passwords" or the password was changed, the promise resolves.
+            .then((data: IChangePasswordSuccess & { autogenPasswords: boolean }) => {
+                // If the operator clicked "Random Passwords", data.autogenPasswords will be true
+                if (data.autogenPasswords) {
+                    this.changePasswordAutogen();
+                }
+                else {
+                    this.changePasswordSuccess(data);   // IasDialogService passes the data intact.
+                }
+            }, noop);
+    }
+
     clearOtpSecret(): void {
         if (this.buttonDisabled('clearOtpSecret')) {
             return;
@@ -264,12 +364,6 @@ export default class HelpDeskDetailComponent {
             }, (error) => {
                 // TODO: Handle error. NOOP for now will not assign person
             });
-
-        this.configService
-            .getActionButtons()
-            .then((actionButtons: IActionButtons) => {
-                this.actionButtons = actionButtons;
-            });     // TODO: remove this code
     }
 
     refresh(): void {

+ 8 - 2
client/src/helpdesk/helpdesk.module.ts

@@ -27,12 +27,15 @@ import HelpDeskDetailComponent from './helpdesk-detail.component';
 import HelpDeskSearchComponent from './helpdesk-search.component';
 import LocalStorageService from '../services/local-storage.service';
 import ObjectService from '../services/object.service';
-import PasswordSuggestionsDialogController from './password-suggestions.controller';
 import PersonCardComponent from '../peoplesearch/person-card.component';
 import PromiseService from '../services/promise.service';
 import RecentVerificationsDialogController from './recent-verifications-dialog.controller';
 import uxModule from '../ux/ux.module';
 import VerificationsDialogController from './verifications-dialog.controller';
+import AutogenChangePasswordController from '../changepassword/autogen-change-password.controller';
+import RandomChangePasswordController from '../changepassword/random-change-password.controller';
+import SuccessChangePasswordController from '../changepassword/success-change-password.controller';
+import TypeChangePasswordController from '../changepassword/type-change-password.controller';
 
 require('../peoplesearch/peoplesearch.scss');
 
@@ -45,8 +48,11 @@ module(moduleName, [
     .component('helpDeskSearch', HelpDeskSearchComponent)
     .component('helpDeskDetail', HelpDeskDetailComponent)
     .component('personCard', PersonCardComponent)
-    .controller('PasswordSuggestionsDialogController', PasswordSuggestionsDialogController)
+    .controller('AutogenChangePasswordController', AutogenChangePasswordController)
+    .controller('RandomChangePasswordController', RandomChangePasswordController)
     .controller('RecentVerificationsDialogController', RecentVerificationsDialogController)
+    .controller('SuccessChangePasswordController', SuccessChangePasswordController)
+    .controller('TypeChangePasswordController', TypeChangePasswordController)
     .controller('VerificationsDialogController', VerificationsDialogController)
     .filter('dateFilter', DateFilter)
     .service('ObjectService', ObjectService)

+ 0 - 15
client/src/helpdesk/password-suggestions-dialog.scss

@@ -1,15 +0,0 @@
-ias-dialog {
-  &.password-suggestions-dialog {
-    table {
-      td {
-        min-width: 160px;
-        height: 15px;
-
-        div {
-          cursor: pointer;
-          font-family: monospace;
-        }
-      }
-    }
-  }
-}

+ 7 - 0
client/src/i18n/translations_en.json

@@ -12,6 +12,7 @@
   "Button_More": "More",
   "Button_OK": "OK",
   "Button_OTP": "OTP",
+  "Button_Show": "Show",
   "Button_SMS": "SMS",
   "Button_Unlock": "Unlock",
   "Button_Verifications": "Verifications",
@@ -21,19 +22,25 @@
   "Display_CaptchaRefresh": "Refresh",
   "Display_HelpdeskOtpValidation": "Instruct the user to load their mobile authentication app and share the current pass code.",
   "Display_PasswordGeneration": "The following passwords have been randomly generated for you.  These passwords are based on real words to make them easier to remember, but have been modified to make them difficult to guess.",
+  "Display_PasswordPrompt": "Please type your new password",
   "Display_PleaseWait": "Loading...",
+  "Display_Random": "Random",
   "Display_SearchResultsExceeded": "Search results exceeded maximum search size",
   "Display_SearchResultsNone": "No results",
+  "Display_SetRandomPasswordPrompt": "Set a new random password for this user?",
+  "Display_StrengthMeter": "Password Strength",
   "Display_TokenDestination": "Token Destination",
   "Field_DateTime": "Date/Time",
   "Field_Display": "Display",
   "Field_LdapProfile": "LDAP Profile",
   "Field_Method": "Method",
+  "Field_NewPassword": "New Password",
   "Field_Policy": "Policy",
   "Field_Profile": "Profile",
   "Field_Username": "User Name",
   "Long_Title_VerificationSend": "Before this user can be selected, the user's identity must be verified.  Please select a verification method.",
   "Placeholder_Search": "Search",
+  "Title_ChangePassword": "Change Password",
   "Title_Details": "Details",
   "Title_DirectReports": "Direct Report(s)",
   "Title_HelpDesk": "Help Desk",

+ 7 - 16
client/src/services/helpdesk-config.service.dev.ts

@@ -26,7 +26,7 @@ import {ConfigBaseService} from './base-config.service.dev';
 import {IConfigService} from './base-config.service';
 import {
     IActionButtons,
-    IHelpDeskConfigService, IVerificationMap, TOKEN_CHOICE, VERIFICATION_METHOD_LABELS,
+    IHelpDeskConfigService, IVerificationMap, PASSWORD_UI_MODES, TOKEN_CHOICE, VERIFICATION_METHOD_LABELS,
     VERIFICATION_METHOD_NAMES
 } from './helpdesk-config.service';
 
@@ -37,21 +37,8 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
         super($q);
     }
 
-    getActionButtons(): IPromise<IActionButtons> {
-        return this.$q.resolve({
-            'Confirm New User Generation': {
-                name: 'Generate a New User',
-                description: 'Clones the current user'
-            },
-            'Confirm User Merge': {
-                name: 'Merge Two Users',
-                description: 'Merges the current user with another user'
-            }
-        });
-    }
-
-    getPasswordUiMode(): IPromise<string> {
-        return this.$q.resolve('both');
+    getClearResponsesSetting(): IPromise<string> {
+        return this.$q.resolve('ask');
     }
 
     getColumnConfig(): IPromise<any> {
@@ -65,6 +52,10 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
         });
     }
 
+    getPasswordUiMode(): IPromise<string> {
+        return this.$q.resolve(PASSWORD_UI_MODES.BOTH);
+    }
+
     getTokenSendMethod(): IPromise<string> {
         return this.$q.resolve(TOKEN_CHOICE);
     }

+ 12 - 4
client/src/services/helpdesk-config.service.ts

@@ -26,7 +26,7 @@ import IPwmService from './pwm.service';
 import PwmService from './pwm.service';
 import {ConfigBaseService, IConfigService} from './base-config.service';
 
-const ACTION_BUTTONS_CONFIG = 'actions';
+const CLEAR_RESPONSES_CONFIG = 'helpdesk_setting_clearResponses';
 const COLUMN_CONFIG = 'helpdesk_search_columns';
 const MASK_PASSWORDS_CONFIG = 'helpdesk_setting_maskPasswords';
 const PASSWORD_UI_MODE_CONFIG = 'helpdesk_setting_PwUiMode';
@@ -38,6 +38,14 @@ const VERIFICATION_FORM_CONFIG = 'verificationForm';
 const VERIFICATION_METHODS_CONFIG = 'verificationMethods';
 export const TOKEN_CHOICE = 'CHOICE_SMS_EMAIL';
 
+export const PASSWORD_UI_MODES = {
+    NONE: 'NONE',
+    AUTOGEN: 'AUTOGEN',
+    RANDOM: 'RANDOM',
+    TYPE: 'TYPE',
+    BOTH: 'BOTH'
+};
+
 export const VERIFICATION_METHOD_NAMES = {
     ATTRIBUTES: 'ATTRIBUTES',
     EMAIL: 'EMAIL',
@@ -64,7 +72,7 @@ interface IVerificationResponse {
 export type IVerificationMap = {name: string, label: string}[];
 
 export interface IHelpDeskConfigService extends IConfigService {
-    getActionButtons(): IPromise<IActionButtons>;
+    getClearResponsesSetting(): IPromise<string>;
     getPasswordUiMode(): IPromise<string>;
     getTokenSendMethod(): IPromise<string>;
     getVerificationAttributes(): IPromise<IVerificationMap>;
@@ -80,8 +88,8 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
         super($http, $log, $q, pwmService);
     }
 
-    getActionButtons(): IPromise<IActionButtons> {
-        return this.getValue(ACTION_BUTTONS_CONFIG);
+    getClearResponsesSetting(): IPromise<string> {
+        return this.getValue(CLEAR_RESPONSES_CONFIG);
     }
 
     getColumnConfig(): IPromise<any> {

+ 11 - 2
client/src/services/helpdesk.service.dev.ts

@@ -26,7 +26,7 @@ import {
 } from './helpdesk.service';
 import {IPromise, IQService, ITimeoutService, IWindowService} from 'angular';
 
-const SIMULATED_RESPONSE_TIME = 300;
+const SIMULATED_RESPONSE_TIME = 30;
 
 export default class HelpDeskService implements IHelpDeskService {
     PWM_GLOBAL: any;
@@ -446,6 +446,15 @@ export default class HelpDeskService implements IHelpDeskService {
         return this.simulateResponse({ destination: 'bcarrolj@paypal.com' });
     }
 
+    setPassword(userKey: string, random: boolean, password?: string): IPromise<ISuccessResponse> {
+        if (random) {
+            return this.simulateResponse({ successMessage: 'Random password successfully set!' });
+        }
+        else {
+            return this.simulateResponse({ successMessage: 'Password successfully set!' });
+        }
+    }
+
     private simulateResponse<T>(data: T): IPromise<T> {
         let self = this;
 
@@ -475,6 +484,6 @@ export default class HelpDeskService implements IHelpDeskService {
     }
 
     get showStrengthMeter(): boolean {
-        return false;
+        return true;
     }
 }

+ 18 - 0
client/src/services/helpdesk.service.ts

@@ -45,6 +45,7 @@ export interface IHelpDeskService {
     getRandomPassword(userKey: string): IPromise<IRandomPasswordResponse>;
     getRecentVerifications(): IPromise<IRecentVerifications>;
     sendVerificationToken(userKey: string, choice: string): IPromise<IVerificationTokenResponse>;
+    setPassword(userKey: string, random: boolean, password?: string): IPromise<ISuccessResponse>;
     unlockIntruder(userKey: string): IPromise<ISuccessResponse>;
     validateVerificationData(userKey: string, formData: any, tokenData: any): IPromise<IVerificationStatus>;
     showStrengthMeter: boolean;
@@ -215,6 +216,23 @@ export default class HelpDeskService implements IHelpDeskService {
             });
     }
 
+    setPassword(userKey: string, random: boolean, password?: string): IPromise<ISuccessResponse> {
+        let url: string = this.pwmService.getServerUrl('setPassword');
+        let data: any = { username: userKey };
+        if (random) {
+            data.random = true;
+        }
+        else {
+            data.password = password;
+        }
+
+        return this.pwmService
+            .httpRequest(url, { data: data })
+            .then((result: ISuccessResponse) => {
+                return this.$q.resolve(result);
+            });
+    }
+
     unlockIntruder(userKey: string): IPromise<ISuccessResponse> {
         let url: string = this.pwmService.getServerUrl('unlockIntruder');
         url += `&userKey=${userKey}`;