瀏覽代碼

Continue implementing change password dialog on HelpDesk detail page

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

+ 8 - 11
client/src/changepassword/type-change-password.component.html

@@ -31,8 +31,8 @@
         <tbody>
         <tbody>
             <tr>
             <tr>
                 <td>
                 <td>
-                    <input ng-model="$ctrl.password1" ng-hide="$ctrl.password1Masked" type="text">
-                    <input ng-model="$ctrl.password1" ng-show="$ctrl.password1Masked" type="password">
+                    <input ng-model="$ctrl.password1" ng-hide="$ctrl.passwordMasked" type="text">
+                    <input ng-model="$ctrl.password1" ng-show="$ctrl.passwordMasked" type="password">
                     <mf-icon-button
                     <mf-icon-button
                         icon="password_thin"
                         icon="password_thin"
                         id="password-icon1"
                         id="password-icon1"
@@ -42,17 +42,14 @@
                         ng-show="!!$ctrl.password1"></mf-icon-button>
                         ng-show="!!$ctrl.password1"></mf-icon-button>
                 </td>
                 </td>
                 <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 ng-bind="$ctrl.strength" ng-if="$ctrl.showStrengthMeter" ng-show="!!$ctrl.password1">
                     </div>
                     </div>
                 </td>
                 </td>
             </tr>
             </tr>
             <tr>
             <tr>
                 <td>
                 <td>
-                    <input ng-model="$ctrl.password2" ng-hide="$ctrl.password2Masked" type="text">
-                    <input ng-model="$ctrl.password2" ng-show="$ctrl.password2Masked" type="password">
+                    <input ng-model="$ctrl.password2" ng-hide="$ctrl.passwordMasked" type="text">
+                    <input ng-model="$ctrl.password2" ng-show="$ctrl.passwordMasked" type="password">
                     <mf-icon-button
                     <mf-icon-button
                         icon="password_thin"
                         icon="password_thin"
                         id="password-icon2"
                         id="password-icon2"
@@ -62,8 +59,8 @@
                         ng-show="!!$ctrl.password2"></mf-icon-button>
                         ng-show="!!$ctrl.password2"></mf-icon-button>
                 </td>
                 </td>
                 <td>
                 <td>
-                    <span>check</span>
-                    <span>X</span>
+                    <span ng-show="$ctrl.matchStatus==='MATCH'">check</span>
+                    <span ng-show="$ctrl.matchStatus==='NO_MATCH'">X</span>
                 </td>
                 </td>
             </tr>
             </tr>
         </tbody>
         </tbody>
@@ -72,7 +69,7 @@
     <div class="ias-actions">
     <div class="ias-actions">
         <mf-button ng-click="$ctrl.chooseTypedPassword()"
         <mf-button ng-click="$ctrl.chooseTypedPassword()"
                    ng-disabled="!$ctrl.passwordAcceptable">{{ 'Button_ChangePassword' | translate }}</mf-button>
                    ng-disabled="!$ctrl.passwordAcceptable">{{ 'Button_ChangePassword' | translate }}</mf-button>
-        <mf-button ng-click="$ctrl.status = 'autogen'"
+        <mf-button ng-click="$ctrl.onClickRandomPasswords()"
                    ng-if="$ctrl.passwordUiMode === 'BOTH'">{{ 'Title_RandomPasswords' | translate }}
                    ng-if="$ctrl.passwordUiMode === 'BOTH'">{{ 'Title_RandomPasswords' | translate }}
         </mf-button>
         </mf-button>
     </div>
     </div>

+ 63 - 14
client/src/changepassword/type-change-password.controller.ts

@@ -22,48 +22,59 @@
 
 
 
 
 import {IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
 import {IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
-import {IQService, IScope} from 'angular';
+import {IQService, IScope, IWindowService} from 'angular';
 import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
 import {IHelpDeskConfigService} from '../services/helpdesk-config.service';
 import DialogService from '../ux/ias-dialog.service';
 import DialogService from '../ux/ias-dialog.service';
 import {IChangePasswordSuccess} from './success-change-password.controller';
 import {IChangePasswordSuccess} from './success-change-password.controller';
+import {IPasswordService, IValidatePasswordData} from '../services/password.service';
 
 
 require('changepassword/type-change-password.component.scss');
 require('changepassword/type-change-password.component.scss');
 
 
+const EMPTY_MATCH_STATUS = 'EMPTY';
+
 export default class TypeChangePasswordController {
 export default class TypeChangePasswordController {
     passwordAcceptable: boolean;
     passwordAcceptable: boolean;
     maskPasswords: boolean;
     maskPasswords: boolean;
+    matchStatus: string;
     message: string;
     message: string;
     password1: string;
     password1: string;
     password2: string;
     password2: string;
-    password1Masked: boolean;
-    password2Masked: boolean;
+    passwordMasked: boolean;
     passwordUiMode: string;
     passwordUiMode: string;
     passwordSuggestions: string[];
     passwordSuggestions: string[];
     showStrengthMeter: boolean;
     showStrengthMeter: boolean;
-    strength = 'Very Strong';
+    strength: number;
 
 
     static $inject = [
     static $inject = [
         '$q',
         '$q',
         '$scope',
         '$scope',
+        '$window',
         'ConfigService',
         'ConfigService',
         'HelpDeskService',
         'HelpDeskService',
         'IasDialogService',
         'IasDialogService',
+        'PasswordService',
         'personUsername',
         'personUsername',
         'personUserKey',
         'personUserKey',
         'translateFilter'
         'translateFilter'
     ];
     ];
     constructor(private $q: IQService,
     constructor(private $q: IQService,
                 private $scope: IScope,
                 private $scope: IScope,
+                private $window: IWindowService,
                 private configService: IHelpDeskConfigService,
                 private configService: IHelpDeskConfigService,
                 private HelpDeskService: IHelpDeskService,
                 private HelpDeskService: IHelpDeskService,
                 private IasDialogService: DialogService,
                 private IasDialogService: DialogService,
+                private passwordService: IPasswordService,
                 private personUsername: string,
                 private personUsername: string,
                 private personUserKey: string,
                 private personUserKey: string,
                 private translateFilter: (id: string) => string) {
                 private translateFilter: (id: string) => string) {
+        this.password1 = '';
+        this.password2 = '';
         this.passwordAcceptable = true;
         this.passwordAcceptable = true;
         this.passwordSuggestions = Array(20).fill('');
         this.passwordSuggestions = Array(20).fill('');
+        this.matchStatus = EMPTY_MATCH_STATUS;
         this.message = translateFilter('Display_PasswordPrompt');
         this.message = translateFilter('Display_PasswordPrompt');
         this.showStrengthMeter = HelpDeskService.showStrengthMeter;
         this.showStrengthMeter = HelpDeskService.showStrengthMeter;
+        this.strength = 0;
 
 
         let promise = this.$q.all([
         let promise = this.$q.all([
             this.configService.getPasswordUiMode(),
             this.configService.getPasswordUiMode(),
@@ -72,18 +83,23 @@ export default class TypeChangePasswordController {
         promise.then((result) => {
         promise.then((result) => {
             this.passwordUiMode = result[0];
             this.passwordUiMode = result[0];
             this.maskPasswords = result[1];
             this.maskPasswords = result[1];
-            this.password1Masked = this.maskPasswords;
-            this.password2Masked = this.maskPasswords;
+            this.passwordMasked = this.maskPasswords;
         });
         });
 
 
-        // update display (TODO)
+        // Update dialog whenever a password field changes
         this.$scope.$watch('$ctrl.password1', (newValue, oldValue) => {
         this.$scope.$watch('$ctrl.password1', (newValue, oldValue) => {
             if (newValue !== oldValue) {
             if (newValue !== oldValue) {
-                // update display (TODO; first or second?)
-
                 if (this.password2.length) {
                 if (this.password2.length) {
-                    this.password2 = '';        // TODO: should we do this.$scope.applyAsync?
+                    this.password2 = '';
                 }
                 }
+
+                this.updateDialog();
+            }
+        });
+
+        this.$scope.$watch('$ctrl.password2', (newValue, oldValue) => {
+            if (newValue !== oldValue) {
+                this.updateDialog();
             }
             }
         });
         });
     }
     }
@@ -106,11 +122,44 @@ export default class TypeChangePasswordController {
         this.IasDialogService.close({ autogenPasswords: true });
         this.IasDialogService.close({ autogenPasswords: true });
     }
     }
 
 
-    togglePassword1Masked() {
-        this.password1Masked = !this.password1Masked;
+    togglePasswordMasked() {
+        this.passwordMasked = !this.passwordMasked;
     }
     }
 
 
-    togglePassword2Masked() {
-        this.password2Masked = !this.password2Masked;
+    updateDialog() {
+        this.passwordService.validatePassword(this.password1, this.password2, this.personUserKey)
+            .onResult(
+                (data: IValidatePasswordData) => {
+                    if (data.version !== 2) {
+                        // TODO: error message - '[ unexpected version string from server ]'
+                    }
+
+                    this.passwordAcceptable = data.passed && data.match === 'MATCH';
+                    this.matchStatus = data.match;
+                    this.message = data.message;
+
+                    if (!this.password1) {
+                        this.strength = 0;
+                    }
+                    if (data.strength < 20) {
+                        this.strength = 1;
+                    }
+                    else if (data.strength < 45) {
+                        this.strength = 2;
+                    }
+                    else if (data.strength < 70) {
+                        this.strength = 3;
+                    }
+                    else if (data.strength < 100) {
+                        this.strength = 4;
+                    }
+                    else {
+                        this.strength = 5;
+                    }
+                },
+                (message: string) => {
+                    this.message = message;
+                }
+            );
     }
     }
 }
 }

+ 2 - 0
client/src/helpdesk/main.dev.ts

@@ -27,6 +27,7 @@ import uiRouter from '@uirouter/angularjs';
 import PeopleService from '../services/people.service.dev';
 import PeopleService from '../services/people.service.dev';
 import HelpDeskConfigService from '../services/helpdesk-config.service.dev';
 import HelpDeskConfigService from '../services/helpdesk-config.service.dev';
 import HelpDeskService from '../services/helpdesk.service.dev';
 import HelpDeskService from '../services/helpdesk.service.dev';
+import PasswordService from '../services/password.service.dev';
 
 
 // fontgen-loader needs this :(
 // fontgen-loader needs this :(
 require('../icons.json');
 require('../icons.json');
@@ -43,6 +44,7 @@ module('app', [
     }])
     }])
     .config(routes)
     .config(routes)
     .service('HelpDeskService', HelpDeskService)
     .service('HelpDeskService', HelpDeskService)
+    .service('PasswordService', PasswordService)
     .service('PeopleService', PeopleService)
     .service('PeopleService', PeopleService)
     .service('ConfigService', HelpDeskConfigService);
     .service('ConfigService', HelpDeskConfigService);
 
 

+ 2 - 0
client/src/helpdesk/main.ts

@@ -29,6 +29,7 @@ import TranslationsLoaderFactory from '../services/translations-loader.factory';
 import uiRouter from '@uirouter/angularjs';
 import uiRouter from '@uirouter/angularjs';
 import HelpDeskConfigService from '../services/helpdesk-config.service';
 import HelpDeskConfigService from '../services/helpdesk-config.service';
 import HelpDeskService from '../services/helpdesk.service';
 import HelpDeskService from '../services/helpdesk.service';
+import PasswordService from '../services/password.service';
 
 
 // fontgen-loader needs this :(
 // fontgen-loader needs this :(
 require('../icons.json');
 require('../icons.json');
@@ -51,6 +52,7 @@ module('app', [
                 .forceAsyncReload(true);
                 .forceAsyncReload(true);
         }])
         }])
     .service('HelpDeskService', HelpDeskService)
     .service('HelpDeskService', HelpDeskService)
+    .service('PasswordService', PasswordService)
     .service('PeopleService', PeopleService)
     .service('PeopleService', PeopleService)
     .service('PwmService', PwmService)
     .service('PwmService', PwmService)
     .service('ConfigService', HelpDeskConfigService)
     .service('ConfigService', HelpDeskConfigService)

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

@@ -23,6 +23,11 @@
   "Display_HelpdeskOtpValidation": "Instruct the user to load their mobile authentication app and share the current pass code.",
   "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_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_PasswordPrompt": "Please type your new password",
+  "Display_PasswordStrengthHigh": "Strength: <b>Strong</b>",
+  "Display_PasswordStrengthVeryHigh": "Strength: <b>Very Strong</b>",
+  "Display_PasswordStrengthLow": "Strength: <b>Weak</b>",
+  "Display_PasswordStrengthVeryLow": "Strength: <b>Very Weak</b>",
+  "Display_PasswordStrengthMedium": "Strength: <b>Good</b>",
   "Display_PleaseWait": "Loading...",
   "Display_PleaseWait": "Loading...",
   "Display_Random": "Random",
   "Display_Random": "Random",
   "Display_SearchResultsExceeded": "Search results exceeded maximum search size",
   "Display_SearchResultsExceeded": "Search results exceeded maximum search size",

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

@@ -479,6 +479,8 @@ export default class HelpDeskService implements IHelpDeskService {
         return this.simulateResponse({ successMessage: 'Unlock successful.' });
         return this.simulateResponse({ successMessage: 'Unlock successful.' });
     }
     }
 
 
+
+
     validateVerificationData(userKey: string, data: any, method: string): IPromise<IVerificationStatus> {
     validateVerificationData(userKey: string, data: any, method: string): IPromise<IVerificationStatus> {
         return this.simulateResponse({ passed: true });
         return this.simulateResponse({ passed: true });
     }
     }

+ 102 - 0
client/src/services/password.service.dev.ts

@@ -0,0 +1,102 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 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
+ */
+
+const RESPONSE_PROGRESS_WAIT_MS = 10;
+const RESPONSE_WAIT_MS = 100;
+const STRENGTH_PER_PASSWORD_CHARACTER = 10;
+const MAX_STRENGTH = 100;
+const STRENGTH_REQUIRED = 40;
+
+import {IPasswordService, IValidatePasswordResultsFunction} from './password.service';
+import {ILogService, ITimeoutService} from 'angular';
+
+export default class PasswordService implements IPasswordService {
+
+    static $inject = ['$log', '$timeout'];
+    constructor(private $log: ILogService, private $timeout: ITimeoutService) {
+    }
+
+    validatePassword(password1: string, password2: string, userKey: string): IValidatePasswordResultsFunction {
+        let resultFunctions = {
+            dataCallback: this.$log.info.bind(this.$log),
+            messageCallback: this.$log.error.bind(this.$log)
+        };
+
+        let strength = Math.min((password1.length * STRENGTH_PER_PASSWORD_CHARACTER), MAX_STRENGTH);
+        let match = (password1 === password2);
+        let message: string = null;
+        let passed = (strength >= STRENGTH_REQUIRED);
+
+        if (!password1) {
+            message = 'Please type your new password';
+        }
+        if (!passed) {
+            message = 'New password is too short';
+        }
+        else if (!password2) {
+            message = 'Password meets requirements, please type confirmation password';
+        }
+        else if (!match) {
+            message = 'Passwords do not match';
+        }
+        else {
+            message = 'New password accepted, please click change password';
+        }
+
+        let matchStatus: string = null;
+        if (!password1) {
+            matchStatus = 'EMPTY';
+        }
+        else {
+            matchStatus = match ? 'MATCH' : 'NO_MATCH';
+        }
+
+        let data = {
+            version: 2,
+            strength: strength,
+            match: matchStatus,
+            message: message,
+            passed: passed,
+            errorCode: 0
+        };
+
+        this.$timeout(() => {
+            resultFunctions.messageCallback('Checking Password...');
+        }, RESPONSE_PROGRESS_WAIT_MS);
+
+        this.$timeout(() => {
+            resultFunctions.dataCallback(data);
+        }, RESPONSE_WAIT_MS);
+
+        return {
+            onResult: (dataCallback, messageCallback) => {
+                if (dataCallback) {
+                    resultFunctions.dataCallback = dataCallback;
+                }
+
+                if (messageCallback) {
+                    resultFunctions.messageCallback = messageCallback;
+                }
+            }
+        };
+    }
+}

+ 76 - 0
client/src/services/password.service.ts

@@ -0,0 +1,76 @@
+/*
+ * Password Management Servlets (PWM)
+ * http://www.pwm-project.org
+ *
+ * Copyright (c) 2006-2009 Novell, Inc.
+ * Copyright (c) 2009-2018 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 {ILogService, IWindowService} from 'angular';
+import {IPwmService} from './pwm.service';
+
+export interface IPasswordService {
+    validatePassword(password1: string, password2: string, userKey: string): IValidatePasswordResultsFunction;
+}
+
+export interface IValidatePasswordResultsFunction {
+    onResult: (
+        dataCallback: (IValidatePasswordData) => void,
+        messageCallback: (message: string) => void
+    ) => void;
+}
+
+export interface IValidatePasswordData {
+    version: number;
+    strength: number;
+    match: string;
+    message: string;
+    passed: boolean;
+    errorCode: number;
+}
+
+export default class PasswordService implements IPasswordService {
+    PWM_MAIN: any;
+
+    static $inject = [ '$log', '$window', 'pwmService', 'translateFilter' ];
+    constructor(private $log: ILogService,
+                private $window: IWindowService,
+                private pwmService: IPwmService,
+                private translateFilter: (id: string) => string) {
+        if ($window['PWM_MAIN']) {
+            this.PWM_MAIN = $window['PWM_MAIN'];
+        }
+        else {
+            this.$log.warn('PWM_MAIN is not defined on window');
+        }
+    }
+
+    validatePassword(password1: string, password2: string, userKey: string): IValidatePasswordResultsFunction {
+        let data = { password1: password1, password2: password2, userDN: userKey };
+        let processResultsFunction = null;
+        let validationProps = {
+            messageWorking: this.translateFilter('Display_CheckingPassword'),
+            processResultsFunction: processResultsFunction,
+            readDataFunction: () => data,
+            serviceURL: this.pwmService.getServerUrl('checkPassword')
+        };
+        this.PWM_MAIN.pwmFormValidator(validationProps);
+
+        return null;
+    }
+}