瀏覽代碼

Implement Change Password dialog on HelpDesk detail page - part 2 (dialog to set password randomly, clear answers dialog)

Joseph White 7 年之前
父節點
當前提交
c94632b3b6

+ 27 - 8
client/src/helpdesk/helpdesk-detail.component.ts

@@ -24,6 +24,7 @@
 import {Component} from '../component';
 import {IButtonInfo, IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
 import {IScope, ui} from '@types/angular';
+import {noop} from 'angular';
 import {IActionButtons, IHelpDeskConfigService} from '../services/helpdesk-config.service';
 import DialogService from '../ux/ias-dialog.service';
 import {IPeopleService} from '../services/people.service';
@@ -43,7 +44,6 @@ const STATUS_SUCCESS = 'success';
     templateUrl: require('helpdesk/helpdesk-detail.component.html')
 })
 export default class HelpDeskDetailComponent {
-    actionButtons: IActionButtons;
     person: any;
     personCard: IPerson;
     photosEnabled: boolean;
@@ -90,9 +90,34 @@ export default class HelpDeskDetailComponent {
                 controller: 'PasswordSuggestionsDialogController as $ctrl',
                 templateUrl: passwordSuggestionsDialogTemplateUrl,
                 locals: {
+                    personUsername: this.person.userDisplayName,
                     personUserKey: this.getUserKey(),
                 }
-            });
+            })
+            .then(() => {
+                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
+                    });
+            }, noop);
     }
 
     clearOtpSecret(): void {
@@ -264,12 +289,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 {

+ 70 - 20
client/src/helpdesk/password-suggestions-dialog.html

@@ -20,34 +20,84 @@
   ~ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
   -->
 
-<ias-dialog class="password-suggestions-dialog">
-    <div class="ias-dialog-header">
-        <div class="ias-title" ng-bind="'Title_RandomPasswords' | translate"></div>
+<ias-dialog class="password-suggestions-dialog" ng-switch="$ctrl.status">
+
+    <div ng-switch-when="confirm-random">
+        <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>
+    </div>
+
+    <div ng-switch-when="type">
+        <div class="ias-dialog-header">
+            <div class="ias-title" ng-bind="('Title_ChangePassword' | translate) + ' - ' + $ctrl.personUsername"></div>
+        </div>
+        <div class="ias-dialog-body">
+            <!--Password list
+            First pw   strength
+            Second pw  checkmark-->
+        </div>
+        <div class="ias-actions">
+            <mf-button ng-click="$ctrl.chooseTypedPassword()">{{ 'Button_ChangePassword' | translate }}</mf-button>
+            <mf-button ng-click="$ctrl.status = 'autogen'"
+                       ng-if="$ctrl.passwordUiMode === 'both'">{{ 'Title_RandomPasswords' | translate }}</mf-button>
+        </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>
-                </td>
-            </tr>
-            </tbody>
-        </table>
+    <div ng-switch-when="autogen">
+        <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 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.populatePasswordSuggestions()"
+                       ng-disabled="$ctrl.fetchingRandoms">{{ 'Button_More' | translate }}</mf-button>
+            <mf-button ng-click="cancel()">{{ 'Button_Cancel' | translate }}</mf-button>
+        </div>
     </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>
+    <div ng-switch-when="finished">
+        <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.chosenPassword" 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>
     </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>

+ 98 - 21
client/src/helpdesk/password-suggestions.controller.ts

@@ -21,47 +21,99 @@
  */
 
 
-import {IHelpDeskService, IRandomPasswordResponse} from '../services/helpdesk.service';
+import {IHelpDeskService, IRandomPasswordResponse, ISuccessResponse} from '../services/helpdesk.service';
 import {IPromise, IQService} from 'angular';
+import {IHelpDeskConfigService, PASSWORD_UI_MODES} from '../services/helpdesk-config.service';
+import DialogService from '../ux/ias-dialog.service';
 
-let RANDOM_MAPPING_LENGTH = 20;
+const RANDOM_MAPPING_SIZE = 20;
+const STATUS_AUTOGEN = 'autogen';
+const STATUS_CONFIRM_RANDOM = 'confirm-random';
+const STATUS_FINISHED = 'finished';
+const STATUS_TYPE = 'type';
 
 require('helpdesk/password-suggestions-dialog.scss');
 
 export default class PasswordSuggestionsDialogController {
+    chosenPassword: string;
+    clearResponsesSetting: string;
     fetchingRandoms: boolean;
+    maskPasswords: boolean;
+    passwordMasked: boolean;
     passwordSuggestions: string[];
+    passwordUiMode: string;
+    status: string;
+    successMessage: string;
+    // this.HelpDeskService.showStrengthMeter;
 
-    static $inject = [ '$q', 'HelpDeskService', 'personUserKey' ];
-    constructor(private $q: IQService, private HelpDeskService: IHelpDeskService, private personUserKey: string) {
+    static $inject = [
+        '$q',
+        'ConfigService',
+        'HelpDeskService',
+        'IasDialogService',
+        'personUsername',
+        'personUserKey',
+        'translateFilter'
+    ];
+    constructor(private $q: IQService,
+                private configService: IHelpDeskConfigService,
+                private HelpDeskService: IHelpDeskService,
+                private IasDialogService: DialogService,
+                private personUsername: string,
+                private personUserKey: string,
+                private translateFilter: (id: string) => string) {
         this.passwordSuggestions = Array(20).fill('');
-        this.fetchRandoms();
+
+        let promise = this.$q.all([
+            this.configService.getClearResponsesSetting(),
+            this.configService.getPasswordUiMode(),
+            this.configService.maskPasswordsEnabled()
+        ]);
+        promise.then((result) => {
+            this.clearResponsesSetting = result[0];
+            this.passwordUiMode = result[1];
+            this.maskPasswords = result[2];
+            this.passwordMasked = this.maskPasswords;   // Set now instead of when we set status to STATUS_FINISHED
+            if (this.passwordUiMode === PASSWORD_UI_MODES.AUTOGEN) {
+                this.status = STATUS_AUTOGEN;
+                this.populatePasswordSuggestions();
+            }
+            else if (this.passwordUiMode === PASSWORD_UI_MODES.RANDOM) {
+                this.status = STATUS_CONFIRM_RANDOM;
+            }
+            else if (this.passwordUiMode === PASSWORD_UI_MODES.BOTH || this.passwordUiMode === PASSWORD_UI_MODES.TYPE) {
+                this.status = STATUS_TYPE;
+            }
+            else {
+                throw new Error('Password type unsupported!');  // TODO: best way to do this?
+            }
+        });
     }
 
-    onChoosePassword(index: number) {
-        let password = this.passwordSuggestions[index];
-        // change password
+    chooseTypedPassword() {     // todo: should this be merged with onChoosePasswordSuggestion?
+        this.HelpDeskService.setPassword(this.personUserKey, false, this.chosenPassword)
+            .then((result: ISuccessResponse) => {
+                this.status = STATUS_FINISHED;
+                this.successMessage = result.successMessage;
+            });
     }
 
-    onMoreRandomsButtonClick() {
-        this.fetchRandoms();
+    clearAnswers() {
+        this.IasDialogService.close();
     }
 
-    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;
-        });
+    confirmSetRandomPassword() {
+        this.HelpDeskService.setPassword(this.personUserKey, true)
+            .then((result: ISuccessResponse) => {
+                this.chosenPassword = '[' + this.translateFilter('Display_Random') +  ']';
+                this.status = STATUS_FINISHED;
+                this.successMessage = result.successMessage;
+            });
     }
 
     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 +122,15 @@ export default class PasswordSuggestionsDialogController {
         return map;
     }
 
+    onChoosePasswordSuggestion(index: number) {
+        this.chosenPassword = this.passwordSuggestions[index];
+        this.HelpDeskService.setPassword(this.personUserKey, false, this.chosenPassword)
+            .then((result: ISuccessResponse) => {
+                this.status = STATUS_FINISHED;
+                this.successMessage = result.successMessage;
+            });
+    }
+
     passwordSuggestionFactory(index: number): any {
         return () => {
             return this.HelpDeskService.getRandomPassword(this.personUserKey).then(
@@ -79,4 +140,20 @@ 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;
+        });
+    }
+
+    togglePasswordMasked() {
+        this.passwordMasked = !this.passwordMasked;
+    }
 }

+ 5 - 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",
@@ -22,18 +23,22 @@
   "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_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_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.RANDOM);
+    }
+
     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> {

+ 10 - 1
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;
 

+ 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}`;