浏览代码

Merge remote-tracking branch 'origin/master'

jrivard@gmail.com 6 年之前
父节点
当前提交
1288dd9602

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

@@ -17,6 +17,8 @@
   "Button_More": "More",
   "Button_OK": "OK",
   "Button_OTP": "OTP",
+  "Button_TokenVerification": "Token Verification",
+  "Button_SendToken": "Send Token",
   "Button_Remove": "Remove",
   "Button_Show": "Show",
   "Button_SMS": "SMS",
@@ -42,6 +44,8 @@
   "Display_StrengthMeter": "Password Strength",
   "Display_TokenDestination": "Token Destination",
   "Display_ViewDetails": "View Details",
+  "Display_EmailPrefix": "Email - ",
+  "Display_SmsPrefix": "SMS - ",
   "Field_DateTime": "Date/Time",
   "Field_Display": "Display",
   "Field_LdapProfile": "LDAP Profile",

+ 1 - 1
client/src/modules/helpdesk/helpdesk-detail.component.ts

@@ -430,7 +430,7 @@ export default class HelpDeskDetailComponent {
                 templateUrl: verificationsDialogTemplateUrl,
                 locals: {
                     personUserKey: this.getUserKey(),
-                    search: false
+                    showRequiredOnly: false
                 }
             });
     }

+ 1 - 1
client/src/modules/helpdesk/helpdesk-search-base.component.ts

@@ -332,7 +332,7 @@ export default abstract class HelpDeskSearchBaseComponent {
                 templateUrl: verificationsDialogTemplateUrl,
                 locals: {
                     personUserKey: person.userKey,
-                    search: true
+                    showRequiredOnly: true
                 }
             });
     }

+ 16 - 0
client/src/modules/helpdesk/verifications-dialog.component.scss

@@ -0,0 +1,16 @@
+#verification-token-destination {
+    margin: 0;
+    width: 210px;
+}
+
+.token-destination-submitter {
+    display: grid;
+    grid-template-columns: max-content max-content max-content;
+    grid-column-gap: 10px;
+    grid-row-gap: 5px;
+    align-items: center;
+
+    select {
+        grid-column-start: 1;
+    }
+}

+ 77 - 37
client/src/modules/helpdesk/verifications-dialog.controller.ts

@@ -20,14 +20,19 @@
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  */
 
+require('./verifications-dialog.component.scss');
 
 import {ui, ITimeoutService} from 'angular';
 import {
-    IHelpDeskConfigService, IVerificationMap, TOKEN_CHOICE,
+    IHelpDeskConfigService, IVerificationMap, TOKEN_CHOICE, VERIFICATION_METHOD_LABELS,
     VERIFICATION_METHOD_NAMES
 } from '../../services/helpdesk-config.service';
-import {IHelpDeskService, IVerificationTokenResponse} from '../../services/helpdesk.service';
-import {IPerson} from '../../models/person.model';
+import {
+    IHelpDeskService,
+    IVerificationOptions,
+    IVerificationStatus,
+    IVerificationTokenResponse
+} from '../../services/helpdesk.service';
 import ObjectService from '../../services/object.service';
 
 const STATUS_FAILED = 'failed';
@@ -38,6 +43,11 @@ const STATUS_VERIFY = 'verify';
 const STATUS_WAIT = 'wait';
 
 export default class VerificationsDialogController {
+    verificationOptions: IVerificationOptions;
+    tokenDestinationID: string;
+    sendingVerificationToken = false;
+    verificationTokenSent = false;
+
     availableVerificationMethods: IVerificationMap;
     formData: any = {};
     inputs: { name: string, label: string }[];
@@ -56,6 +66,7 @@ export default class VerificationsDialogController {
         'IasDialogService',
         'ObjectService',
         'personUserKey',
+        'showRequiredOnly'
     ];
     constructor(private $state: ui.IStateService,
                 private $timeout: ITimeoutService,
@@ -64,26 +75,44 @@ export default class VerificationsDialogController {
                 private IasDialogService: any,
                 private objectService: ObjectService,
                 private personUserKey: string,
-                private search: boolean) {
+                private showRequiredOnly: boolean) {
+
         this.isDetailsView = (this.$state.current.name === 'details');
         this.status = STATUS_WAIT;
         this.verificationStatus = STATUS_NONE;
         this.viewDetailsEnabled = false;
 
-        if (this.isDetailsView) {
-            this.loadVerificationOptions();
-        }
-        else {
-            this.helpDeskService
-                .checkVerification(this.personUserKey)
-                .then((response) => {
-                    if (response.passed) {
-                        this.gotoDetailsPage();
-                    }
-                    else {
-                        this.loadVerificationOptions();
-                    }
+        this.helpDeskService
+            .checkVerification(this.personUserKey)
+            .then((response: IVerificationStatus) => {
+                this.verificationOptions = response.verificationOptions;
+
+                if (!this.isDetailsView && response.passed) {
+                    // If we're not on the details page already, and verifications have been passed, then just go right
+                    // to the details page:
+                    this.gotoDetailsPage();
+                }
+                else {
+                    this.status = STATUS_SELECT;
+                    this.determineAvailableVerificationMethods();
+                }
+            });
+    }
+
+    determineAvailableVerificationMethods() {
+        this.availableVerificationMethods = [];
+
+        const methodNames: string[] = this.showRequiredOnly ?
+            this.verificationOptions.verificationMethods.required :
+            this.verificationOptions.verificationMethods.optional;
+
+        if (methodNames) {
+            for (let methodName of methodNames) {
+                this.availableVerificationMethods.push({
+                    name: methodName,
+                    label: VERIFICATION_METHOD_LABELS[methodName]
                 });
+            }
         }
     }
 
@@ -100,15 +129,6 @@ export default class VerificationsDialogController {
         });
     }
 
-    private loadVerificationOptions() {
-        this.configService
-            .getVerificationMethods({includeOptional: this.isDetailsView})
-            .then((methods) => {
-                this.status = STATUS_SELECT;
-                this.availableVerificationMethods = methods;
-            });
-    }
-
     selectVerificationMethod(method: string) {
         this.verificationMethod = method;
 
@@ -125,17 +145,13 @@ export default class VerificationsDialogController {
                     }
                 });
         }
-        else if (method === VERIFICATION_METHOD_NAMES.SMS || method === VERIFICATION_METHOD_NAMES.EMAIL) {
-            this.status = STATUS_WAIT;
-            this.configService.getTokenSendMethod()
-                .then((tokenSendMethod) => {
-                    let choice = (tokenSendMethod === TOKEN_CHOICE) ? method.toLowerCase() : null;
-                    return this.helpDeskService.sendVerificationToken(this.personUserKey, choice);
-                })
-                .then((response: any) => {
-                    this.status = STATUS_VERIFY;
-                    this.tokenData = response.data;
-                });
+        else if (method === VERIFICATION_METHOD_NAMES.TOKEN) {
+            this.status = STATUS_VERIFY;
+
+            try {
+                // Select the first destination in the list as default.
+                this.tokenDestinationID = this.verificationOptions.tokenDestinations[0].id;
+            } catch (error) {}
         }
         else if (method === VERIFICATION_METHOD_NAMES.OTP) {
             this.status = STATUS_VERIFY;
@@ -157,6 +173,30 @@ export default class VerificationsDialogController {
                 else {
                     this.verificationStatus = STATUS_FAILED;
                 }
+            })
+            .catch((reason) => {
+                this.verificationStatus = STATUS_FAILED;
+            })
+    }
+
+    onTokenDestinationChanged() {
+        this.verificationTokenSent = false;
+    }
+
+    sendVerificationTokenToDestination() {
+        this.sendingVerificationToken = true;
+        this.verificationTokenSent = false;
+
+        this.helpDeskService.sendVerificationToken(this.personUserKey, this.tokenDestinationID)
+            .then((response) => {
+                this.verificationTokenSent = true;
+            })
+            .catch((reason) => {
+                this.verificationTokenSent = false;
+                alert(reason);
+            })
+            .finally(() => {
+                this.sendingVerificationToken = false;
             });
     }
 

+ 17 - 3
client/src/modules/helpdesk/verifications-dialog.template.html

@@ -65,10 +65,24 @@
                                        ng-model="$ctrl.formData[input.name]">
                             </div>
                         </div>
-                        <div ng-switch-when="EMAIL|SMS" ng-switch-when-separator="|">
-                            <div class="ias-input-container">
+                        <div ng-switch-when="TOKEN" ng-switch-when-separator="|">
+                            <div class="ias-input-container token-destination-submitter">
                                 <label ng-bind="'Display_TokenDestination' | translate"></label>
-                                <input type="text" ng-value="$ctrl.tokenData.destination" readonly>
+
+                                <select id="verification-token-destination" ng-model="$ctrl.tokenDestinationID"
+                                        ng-change="$ctrl.onTokenDestinationChanged()">
+                                    <option ng-repeat="tokenDestination in $ctrl.verificationOptions.tokenDestinations"
+                                            ng-attr-value="{{tokenDestination.id}}">
+                                        {{ tokenDestination.type == 'sms'?'Display_SmsPrefix':'Display_EmailPrefix' | translate}}
+                                        {{tokenDestination.display}}
+                                    </option>
+                                </select>
+                                <ias-button ng-click="$ctrl.sendVerificationTokenToDestination()"
+                                            ng-disabled="$ctrl.sendingVerificationToken">
+                                    {{ 'Button_SendToken' | translate }}
+                                </ias-button>
+                                <div class="loading-gif-25" ng-if="$ctrl.sendingVerificationToken"></div>
+                                <ias-icon icon="status_ok_thick" style="color:#37c26a;" class="ias-success" ng-if="$ctrl.verificationTokenSent"></ias-icon>
                             </div>
                             <div class="ias-input-container">
                                 <label>Token</label>

+ 2 - 49
client/src/services/helpdesk-config.service.ts

@@ -54,15 +54,13 @@ export const PASSWORD_UI_MODES = {
 
 export const VERIFICATION_METHOD_NAMES = {
     ATTRIBUTES: 'ATTRIBUTES',
-    EMAIL: 'EMAIL',
-    SMS: 'SMS',
+    TOKEN: 'TOKEN',
     OTP: 'OTP'
 };
 
 export const VERIFICATION_METHOD_LABELS = {
     ATTRIBUTES: 'Button_Attributes',
-    EMAIL: 'Button_Email',
-    SMS: 'Button_SMS',
+    TOKEN: 'Button_TokenVerification',
     OTP: 'Button_OTP'
 };
 
@@ -84,7 +82,6 @@ export interface IHelpDeskConfigService extends IConfigService {
     getPasswordUiMode(): IPromise<string>;
     getTokenSendMethod(): IPromise<string>;
     getVerificationAttributes(): IPromise<IVerificationMap>;
-    getVerificationMethods(options?: {includeOptional: boolean}): IPromise<IVerificationMap>;
     maskPasswordsEnabled(): IPromise<boolean>;
     verificationsEnabled(): IPromise<boolean>;
     advancedSearchConfig(): IPromise<IAdvancedSearchConfig>;
@@ -117,50 +114,6 @@ export default class HelpDeskConfigService extends ConfigBaseService implements
         return this.getValue(VERIFICATION_FORM_CONFIG);
     }
 
-    private getVerificationMethod(methodName): {name: string, label: string} {
-        return {
-            name: methodName,
-            label: VERIFICATION_METHOD_LABELS[methodName]
-        };
-    }
-
-    getVerificationMethods(options?: {includeOptional: boolean}): IPromise<IVerificationMap> {
-        let promise = this.$q.all([
-            this.getValue(VERIFICATION_METHODS_CONFIG),
-            this.getTokenSendMethod()
-        ]);
-
-        return promise.then((result) => {
-            let availableMethods: string[];
-            if (options && options.includeOptional) {
-                availableMethods = result[0].optional;
-            }
-            else {
-                availableMethods = result[0].required;
-            }
-
-            let tokenSendMethod: string = result[1];
-
-            let verificationMethods: IVerificationMap = [];
-            availableMethods.forEach((method) => {
-                if (method === TOKEN_VERIFICATION_METHOD) {
-                    if (tokenSendMethod === TOKEN_EMAIL_ONLY || tokenSendMethod === TOKEN_CHOICE) {
-                        verificationMethods.push(this.getVerificationMethod(VERIFICATION_METHOD_NAMES.EMAIL));
-                    }
-
-                    if (tokenSendMethod === TOKEN_SMS_ONLY || tokenSendMethod === TOKEN_CHOICE) {
-                        verificationMethods.push(this.getVerificationMethod(VERIFICATION_METHOD_NAMES.SMS));
-                    }
-                }
-                else {
-                    verificationMethods.push(this.getVerificationMethod(method));
-                }
-            });
-
-            return verificationMethods;
-        });
-    }
-
     maskPasswordsEnabled(): IPromise<boolean> {
         return this.getValue(MASK_PASSWORDS_CONFIG);
     }

+ 23 - 8
client/src/services/helpdesk.service.ts

@@ -30,8 +30,7 @@ import {IQuery} from './people.service';
 
 const VERIFICATION_PROCESS_ACTIONS = {
     ATTRIBUTES: 'validateAttributes',
-    EMAIL: 'verifyVerificationToken',
-    SMS: 'verifyVerificationToken',
+    TOKEN: 'verifyVerificationToken',
     OTP: 'validateOtpCode'
 };
 
@@ -77,8 +76,25 @@ interface IValidationStatus extends IVerificationStatus {
     verificationState: string;
 }
 
+export interface IVerificationOptions {
+    verificationMethods: {
+        optional: string[];
+        required: string[];
+    },
+    verificationForm: [{
+        name: string;
+        label: string;
+    }],
+    tokenDestinations: [{
+        id: string;
+        display: string;
+        type: string;
+    }]
+}
+
 export interface IVerificationStatus {
     passed: boolean;
+    verificationOptions: IVerificationOptions;
 }
 
 export interface IVerificationTokenResponse {
@@ -269,13 +285,12 @@ export default class HelpDeskService implements IHelpDeskService {
             });
     }
 
-    sendVerificationToken(userKey: string, choice: string): IPromise<IVerificationTokenResponse> {
+    sendVerificationToken(userKey: string, destinationID: string): IPromise<IVerificationTokenResponse> {
         let url: string = this.pwmService.getServerUrl('sendVerificationToken');
-        let data: any = { userKey: userKey };
-
-        if (choice) {
-            data.method = choice;
-        }
+        let data: any = {
+            userKey: userKey,
+            id: destinationID
+        };
 
         return this.pwmService
             .httpRequest(url, { data: data })

+ 17 - 47
server/src/main/java/password/pwm/http/servlet/helpdesk/HelpdeskServlet.java

@@ -75,6 +75,7 @@ import password.pwm.svc.intruder.IntruderManager;
 import password.pwm.svc.stats.Statistic;
 import password.pwm.svc.stats.StatisticsManager;
 import password.pwm.svc.token.TokenService;
+import password.pwm.svc.token.TokenUtil;
 import password.pwm.util.PasswordData;
 import password.pwm.util.RandomPasswordGenerator;
 import password.pwm.util.java.JavaHelper;
@@ -107,6 +108,7 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * Admin interaction servlet for reset user passwords.
@@ -737,7 +739,7 @@ public class HelpdeskServlet extends ControlledPwmServlet
     private ProcessStatus restSendVerificationTokenRequest(
             final PwmRequest pwmRequest
     )
-            throws IOException, PwmUnrecoverableException, ServletException, ChaiUnavailableException
+            throws IOException, PwmUnrecoverableException, ChaiUnavailableException
     {
         final HelpdeskProfile helpdeskProfile = getHelpdeskProfile( pwmRequest );
 
@@ -754,58 +756,26 @@ public class HelpdeskServlet extends ControlledPwmServlet
                 getChaiUser( pwmRequest, helpdeskProfile, userIdentity ).getChaiProvider()
         );
 
+        final String requestedTokenID = bodyParams.get( "id" );
+
         final TokenDestinationItem tokenDestinationItem;
         {
-            final MessageSendMethod effectiveSendMethod;
-            {
-                final MessageSendMethod configuredSendMethod = helpdeskProfile.readSettingAsEnum( PwmSetting.HELPDESK_TOKEN_SEND_METHOD, MessageSendMethod.class );
-                if ( configuredSendMethod == MessageSendMethod.CHOICE_SMS_EMAIL )
-                {
-                    final String methodParamName = "method";
-                    final String methodParam = bodyParams.getOrDefault( methodParamName, "" );
-                    switch ( methodParam )
-                    {
-                        case "sms":
-                            effectiveSendMethod = MessageSendMethod.SMSONLY;
-                            break;
+            final List<TokenDestinationItem> items = TokenUtil.figureAvailableTokenDestinations(
+                    pwmRequest.getPwmApplication(),
+                    pwmRequest.getSessionLabel(),
+                    pwmRequest.getLocale(),
+                    userInfo,
+                    MessageSendMethod.CHOICE_SMS_EMAIL  );
 
-                        case "email":
-                            effectiveSendMethod = MessageSendMethod.EMAILONLY;
-                            break;
+            final Optional<TokenDestinationItem> selectedTokenDest = TokenDestinationItem.tokenDestinationItemForID( items, requestedTokenID );
 
-                        default:
-                            throw new UnsupportedOperationException( "unknown tokenSendMethod: " + methodParam );
-                    }
-                }
-                else
-                {
-                    effectiveSendMethod = configuredSendMethod;
-                }
+            if ( selectedTokenDest.isPresent() )
+            {
+                tokenDestinationItem = selectedTokenDest.get();
             }
-
-            switch ( effectiveSendMethod )
+            else
             {
-                case SMSONLY:
-                    tokenDestinationItem = TokenDestinationItem.builder()
-                            .id( "0" )
-                            .display( userInfo.getUserSmsNumber() )
-                            .value( userInfo.getUserSmsNumber() )
-                            .type( TokenDestinationItem.Type.sms )
-                            .build();
-                    break;
-
-                case EMAILONLY:
-                    tokenDestinationItem = TokenDestinationItem.builder()
-                            .id( "0" )
-                            .display( userInfo.getUserEmailAddress() )
-                            .value( userInfo.getUserEmailAddress() )
-                            .type( TokenDestinationItem.Type.email )
-                            .build();
-                    break;
-
-                default:
-                    throw new UnsupportedOperationException( "unknown tokenSendMethod: " + effectiveSendMethod );
-
+                throw PwmUnrecoverableException.newException( PwmError.ERROR_INTERNAL, "unknown token id '" + requestedTokenID + "' in request" );
             }
         }