Browse Source

Implement rest of HelpDesk detail page buttons and their respective dialogs and
API calls

Joseph White 7 years ago
parent
commit
3ca65af7e4

+ 62 - 0
client/src/helpdesk/helpdesk-detail-dialog.template.html

@@ -0,0 +1,62 @@
+<!--
+  ~ 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>
+    <div ng-switch="status">
+        <div class="ias-actions" ng-switch-default>
+            <div class="WaitDialogBlank"></div>
+        </div>
+
+        <div ng-switch-when="confirm">
+            <div class="ias-dialog-header">
+                <div class="ias-title" ng-bind="title"></div>
+            </div>
+            <div class="ias-dialog-body">
+                <p ng-bind="text"></p>
+                <p ng-bind="secondaryText" ng-if="!!secondaryText"></p>
+            </div>
+            <div class="ias-actions">
+                <mf-button ng-click="confirm()">{{ 'Button_OK' | translate }}</mf-button>
+                <mf-button ng-click="close()">{{ 'Button_Cancel' | translate }}</mf-button>
+            </div>
+        </div>
+
+        <div ng-switch-when="success">
+            <div class="ias-dialog-header">
+                <div class="ias-title" ng-bind="title"></div>
+            </div>
+            <div class="ias-dialog-body">
+                <p ng-bind="text"></p>
+            </div>
+            <div class="ias-actions">
+                <mf-button ng-click="close()">{{ 'Button_OK' | translate }}</mf-button>
+            </div>
+        </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()">
+    </mf-icon-button>
+</ias-dialog>

+ 9 - 6
client/src/helpdesk/helpdesk-detail.component.html

@@ -92,7 +92,7 @@
                         <td ng-bind="'Field_Profile' | translate"></td>
                         <td ng-bind="$ctrl.person.passwordPolicyID"></td>
                     </tr>
-                    <tr>
+                    <tr class="bottom-border">
                         <td ng-bind="'Field_Display' | translate"></td>
                         <td>
                             <ul>
@@ -100,7 +100,6 @@
                             </ul>
                         </td>
                     </tr>
-                    <hr>
                     <tr ng-repeat="(key, item) in $ctrl.person.passwordPolicyRules">
                         <td ng-bind="key"></td>
                         <td ng-bind="item"></td>
@@ -136,16 +135,20 @@
         <mf-button ng-click="$ctrl.unlockUser()"
                    ng-disabled="$ctrl.buttonDisabled('unlock')"
                    ng-if="$ctrl.buttonVisible('unlock')">{{ 'Button_Unlock' | translate }}</mf-button>
-        <mf-button ng-disabled="$ctrl.buttonDisabled('clearResponses')"
+        <mf-button ng-click="$ctrl.clearResponses()"
+                   ng-disabled="$ctrl.buttonDisabled('clearResponses')"
                    ng-if="$ctrl.buttonVisible('clearResponses')">{{ 'Button_ClearResponses' | translate }}</mf-button>
-        <mf-button ng-disabled="$ctrl.buttonDisabled('clearOtpSecret')"
+        <mf-button ng-click="$ctrl.clearOtpSecret()"
+                   ng-disabled="$ctrl.buttonDisabled('clearOtpSecret')"
                    ng-if="$ctrl.buttonVisible('clearOtpSecret')">{{ 'Button_HelpdeskClearOtpSecret' | translate }}</mf-button>
         <mf-button ng-click="$ctrl.verifyUser()"
                    ng-disabled="$ctrl.buttonDisabled('verification')"
                    ng-if="$ctrl.buttonVisible('verification')">{{ 'Button_Verify' | translate }}</mf-button>
-        <mf-button ng-disabled="$ctrl.buttonDisabled('deleteUser')"
+        <mf-button ng-click="$ctrl.deleteUser()"
+                   ng-disabled="$ctrl.buttonDisabled('deleteUser')"
                    ng-if="$ctrl.buttonVisible('deleteUser')">{{ 'Button_Delete' | translate }}</mf-button>
-        <mf-button ng-repeat="button in $ctrl.person.customButtons"
+        <mf-button ng-click="$ctrl.clickCustomButton(button)"
+                   ng-repeat="button in $ctrl.person.customButtons"
                    ng-attr-title="{{button.description}}">{{ button.label }}</mf-button>
     </div>
 </div>

+ 4 - 0
client/src/helpdesk/helpdesk-detail.component.scss

@@ -59,6 +59,10 @@ help-desk-detail {
           tr {
             height: 25px;
 
+            &.bottom-border {
+              border-bottom: 1px solid #949494;
+            }
+
             td {
               border: none;
               font-size: 12px;

+ 186 - 7
client/src/helpdesk/helpdesk-detail.component.ts

@@ -22,14 +22,20 @@
 
 
 import {Component} from '../component';
-import {IHelpDeskService} from '../services/helpdesk.service';
-import {ui} from '@types/angular';
+import {IButtonInfo, IHelpDeskService, ISuccessResponse} from '../services/helpdesk.service';
+import {IScope, ui} from '@types/angular';
 import {IActionButtons, IHelpDeskConfigService} 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';
 
 let verificationsDialogTemplateUrl = require('./verifications-dialog.template.html');
+let helpdeskDetailDialogTemplateUrl = require('./helpdesk-detail-dialog.template.html');
+
+const STATUS_WAIT = 'wait';
+const STATUS_CONFIRM = 'confirm';
+const STATUS_SUCCESS = 'success';
 
 @Component({
     stylesheetUrl: require('helpdesk/helpdesk-detail.component.scss'),
@@ -81,12 +87,159 @@ export default class HelpDeskDetailComponent {
         alert('Change password dialog');
     }
 
+    clearOtpSecret(): void {
+        if (this.buttonDisabled('clearOtpSecret')) {
+            return;
+        }
+
+        let userKey = this.getUserKey();
+
+        this.IasDialogService
+            .open({
+                controller: [
+                    '$scope',
+                    'HelpDeskService',
+                    'translateFilter',
+                    function ($scope: IScope,
+                              helpDeskService: IHelpDeskService,
+                              translateFilter: (id: string) => string) {
+                        $scope.status = STATUS_CONFIRM;
+                        $scope.title = translateFilter('Button_HelpdeskClearOtpSecret');
+                        $scope.text = translateFilter('Confirm');
+                        $scope.confirm = () => {
+                            $scope.status = STATUS_WAIT;
+                            helpDeskService.clearResponses(userKey).then((data: ISuccessResponse) => {
+                                // TODO - error dialog?
+                                $scope.status = STATUS_SUCCESS;
+                                $scope.text = data.successMessage;
+                            });
+                        };
+                    }
+                ],
+                templateUrl: helpdeskDetailDialogTemplateUrl
+            });
+    }
+
+    clearResponses(): void {
+        if (this.buttonDisabled('clearResponses')) {
+            return;
+        }
+
+        let userKey = this.getUserKey();
+
+        this.IasDialogService
+            .open({
+                controller: [
+                    '$scope',
+                    'HelpDeskService',
+                    'translateFilter',
+                    function ($scope: IScope,
+                              helpDeskService: IHelpDeskService,
+                              translateFilter: (id: string) => string) {
+                        $scope.status = STATUS_CONFIRM;
+                        $scope.title = translateFilter('Button_ClearResponses');
+                        $scope.text = translateFilter('Confirm');
+                        $scope.confirm = () => {
+                            $scope.status = STATUS_WAIT;
+                            helpDeskService.clearResponses(userKey).then((data: ISuccessResponse) => {
+                                // TODO - error dialog?
+                                $scope.status = STATUS_SUCCESS;
+                                $scope.text = data.successMessage;
+                            });
+                        };
+                    }
+                ],
+                templateUrl: helpdeskDetailDialogTemplateUrl
+            });
+    }
+
+    clickCustomButton(button: IButtonInfo): void {
+        // Custom buttons are never disabled
+
+        let userKey = this.getUserKey();
+
+        this.IasDialogService
+            .open({
+                controller: [
+                    '$scope',
+                    'HelpDeskService',
+                    'translateFilter',
+                    function ($scope: IScope,
+                              helpDeskService: IHelpDeskService,
+                              translateFilter: (id: string) => string) {
+                        $scope.status = STATUS_CONFIRM;
+                        $scope.title = translateFilter('Button_Confirm') + ' ' + button.label;
+                        $scope.text = button.description;
+                        $scope.secondaryText = translateFilter('Confirm');
+                        $scope.confirm = () => {
+                            $scope.status = STATUS_WAIT;
+                            helpDeskService.customAction(button.name, userKey).then((data: ISuccessResponse) => {
+                                // TODO - error dialog? (btw this error dialog is slightly different)
+                                $scope.status = STATUS_SUCCESS;
+                                $scope.title = translateFilter('Title_Success');
+                                $scope.secondaryText = null;
+                                $scope.text = data.successMessage;
+                            });
+                        };
+                    }
+                ],
+                templateUrl: helpdeskDetailDialogTemplateUrl
+            });
+    }
+
+    deleteUser(): void {
+        if (this.buttonDisabled('deleteUser')) {
+            return;
+        }
+
+        let self = this;
+        let userKey = this.getUserKey();
+
+        this.IasDialogService
+            .open({
+                controller: [
+                    '$scope',
+                    'HelpDeskService',
+                    'IasDialogService',
+                    'translateFilter',
+                    function ($scope: IScope,
+                              helpDeskService: IHelpDeskService,
+                              IasDialogService: DialogService,
+                              translateFilter: (id: string) => string) {
+                        $scope.status = STATUS_CONFIRM;
+                        $scope.title = translateFilter('Button_Confirm');
+                        $scope.text = translateFilter('Confirm_DeleteUser');
+                        $scope.confirm = () => {
+                            $scope.status = STATUS_WAIT;
+                            helpDeskService.deleteUser(userKey).then((data: ISuccessResponse) => {
+                                // TODO - error dialog?
+                                $scope.status = STATUS_SUCCESS;
+                                $scope.title = translateFilter('Title_Success');
+                                $scope.text = data.successMessage;
+                                $scope.close = () => {
+                                    IasDialogService.close();
+                                    self.gotoSearch();
+                                };
+                            });
+                        };
+                    }
+                ],
+                templateUrl: helpdeskDetailDialogTemplateUrl
+            });
+    }
+
+
+
+    getUserKey(): string {
+        return this.$stateParams['personId'];
+    }
+
     gotoSearch(): void {
         this.$state.go('search');
     }
 
     initialize(): void {
-        const personId = this.$stateParams['personId'];
+        const personId = this.getUserKey();
 
         this.configService.photosEnabled().then((photosEnabled: boolean) => {
             this.photosEnabled = photosEnabled;
@@ -108,19 +261,45 @@ export default class HelpDeskDetailComponent {
             .getActionButtons()
             .then((actionButtons: IActionButtons) => {
                 this.actionButtons = actionButtons;
-            });
+            });     // TODO: remove this code
     }
 
     refresh(): void {
+        this.person = null;
         this.initialize();
     }
 
     unlockUser(): void {
-        if (this.person.accountEnabled) {
+        if (this.buttonDisabled('unlock')) {
             return;
         }
 
-        alert('Unlock user dialog');
+        let userKey = this.getUserKey();
+
+        this.IasDialogService
+            .open({
+                controller: [
+                    '$scope',
+                    'HelpDeskService',
+                    'translateFilter',
+                    function ($scope: IScope,
+                              helpDeskService: IHelpDeskService,
+                              translateFilter: (id: string) => string) {
+                        $scope.status = STATUS_CONFIRM;
+                        $scope.title = translateFilter('Button_Unlock');
+                        $scope.text = translateFilter('Confirm');
+                        $scope.confirm = () => {
+                            $scope.status = STATUS_WAIT;
+                            helpDeskService.unlockIntruder(userKey).then((data: ISuccessResponse) => {
+                                // TODO - error dialog?
+                                $scope.status = STATUS_SUCCESS;
+                                $scope.text = data.successMessage;
+                            });
+                        };
+                    }
+                ],
+                templateUrl: helpdeskDetailDialogTemplateUrl
+            });
     }
 
     verifyUser(): void {
@@ -129,7 +308,7 @@ export default class HelpDeskDetailComponent {
                 controller: 'VerificationsDialogController as $ctrl',
                 templateUrl: verificationsDialogTemplateUrl,
                 locals: {
-                    personUserKey: this.$stateParams['personId'],
+                    personUserKey: this.getUserKey(),
                     search: false
                 }
             });

+ 1 - 1
client/src/helpdesk/recent-verifications-dialog.template.html

@@ -60,7 +60,7 @@
     <mf-icon-button class="ias-dialog-close-button"
                     icon="close_thick"
                     id="close-icon"
-                    ng-attr-title="{{ 'Title_Close' | translate }}"
+                    ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
                     ng-click="close()">
     </mf-icon-button>
 </ias-dialog>

+ 6 - 0
client/src/helpdesk/verifications-dialog.controller.ts

@@ -88,6 +88,12 @@ export default class VerificationsDialogController {
         }
     }
 
+    clickOkButton() {
+        if (this.verificationStatus === STATUS_PASSED) {
+            this.IasDialogService.close();
+        }
+    }
+
     private gotoDetailsPage() {
         this.$timeout(() => {
             this.IasDialogService.close();

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

@@ -40,7 +40,7 @@
                 </div>
             </div>
             <div class="ias-actions">
-                <mf-button ng-click="close()">Cancel</mf-button>
+                <mf-button ng-click="close()">{{ 'Button_Cancel' | translate }}</mf-button>
             </div>
         </div>
 
@@ -93,7 +93,7 @@
                     View Details
                 </mf-button>
                 <mf-button ng-disabled="$ctrl.verificationStatus!=='passed'"
-                           ng-click="close()"
+                           ng-click="$ctrl.clickOkButton()"
                            ng-if="$ctrl.isDetailsView">
                     {{ 'Button_OK' | translate }}
                 </mf-button>
@@ -105,7 +105,7 @@
     <mf-icon-button class="ias-dialog-close-button"
                     icon="close_thick"
                     id="close-icon"
-                    ng-attr-title="{{ 'Title_Close' | translate }}"
+                    ng-attr-title="{{ 'Button_CloseWindow' | translate }}"
                     ng-click="close()">
     </mf-icon-button>
 </ias-dialog>

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

@@ -3,6 +3,8 @@
   "Button_Cancel": "Cancel",
   "Button_ChangePassword": "Change Password",
   "Button_ClearResponses": "Clear Answers",
+  "Button_CloseWindow": "Close Window",
+  "Button_Confirm": "Confirm",
   "Button_Delete": "Delete",
   "Button_Email": "Email",
   "Button_GoBack": "Go Back",
@@ -13,6 +15,8 @@
   "Button_Unlock": "Unlock",
   "Button_Verifications": "Verifications",
   "Button_Verify": "Verify",
+  "Confirm": "Are you sure you wish to proceed?",
+  "Confirm_DeleteUser": "Are you sure you wish to proceed?  If you continue, the selected user will be deleted permanently.  This can not be undone.",
   "Display_CaptchaRefresh": "Refresh",
   "Display_HelpdeskOtpValidation": "Instruct the user to load their mobile authentication app and share the current pass code.",
   "Display_PleaseWait": "Loading...",
@@ -42,6 +46,7 @@
   "Title_RecentVerifications": "Recent Verifications",
   "Title_SecurityResponses": "Security Responses",
   "Title_Status": "Status",
+  "Title_Success": "Success",
   "Title_UserEventHistory": "Password History",
   "Title_ValidateCode": "Validate Code",
   "Title_VerificationSend": "Select verification method"

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

@@ -21,24 +21,50 @@
  */
 
 import {
-    IHelpDeskService, IRecentVerifications, IVerificationStatus,
+    IHelpDeskService, IRecentVerifications, ISuccessResponse, IVerificationStatus,
     IVerificationTokenResponse
 } from './helpdesk.service';
-import {IPromise, IQService, IWindowService} from 'angular';
+import {IPromise, IQService, ITimeoutService, IWindowService} from 'angular';
+
+const SIMULATED_RESPONSE_TIME = 300;
 
 export default class HelpDeskService implements IHelpDeskService {
     PWM_GLOBAL: any;
 
-    static $inject = [ '$q', '$window' ];
-    constructor(private $q: IQService, private $window: IWindowService) {
+    static $inject = [ '$q', '$timeout', '$window' ];
+    constructor(private $q: IQService, private $timeout: ITimeoutService, private $window: IWindowService) {
     }
 
     checkVerification(userKey: string): IPromise<IVerificationStatus> {
-        return this.$q.resolve({ passed: false });
+        return this.simulateResponse({ passed: false });
+    }
+
+    clearOtpSecret(userKey: string): IPromise<ISuccessResponse> {
+        return this.simulateResponse({ successMessage: 'OTP Secret successfully cleared.' });
+    }
+
+    clearResponses(userKey: string): IPromise<ISuccessResponse> {
+        return this.simulateResponse({ successMessage: 'Security answers successfully cleared.' });
+    }
+
+    customAction(actionName: string, userKey: string): IPromise<ISuccessResponse> {
+        if (actionName === 'custom_0') {
+            return this.simulateResponse({ successMessage: 'User successfully cloned.' });
+        }
+        else if (actionName === 'custom_1') {
+            return this.simulateResponse({ successMessage: 'User successfully merged.' });
+        }
+        else {
+            this.$q.reject('Error! Action name doesn\'t exist.');
+        }
+    }
+
+    deleteUser(userKey: string): IPromise<ISuccessResponse> {
+        return this.simulateResponse({ successMessage: 'User successfully deleted.' });
     }
 
     getPerson(userKey: string): IPromise<any> {
-        return this.$q.resolve({
+        return this.simulateResponse({
             'userDisplayName': 'Andrew Astin - aastin - aastin@ad.utopia.netiq.com',
             'userHistory': [
                 {
@@ -358,6 +384,7 @@ export default class HelpDeskService implements IHelpDeskService {
                 'changePassword',
                 'unlock',
                 'clearResponses',
+                'clearOtpSecret',
                 'verification',
                 'deleteUser'
             ],
@@ -365,22 +392,29 @@ export default class HelpDeskService implements IHelpDeskService {
                 'refresh',
                 'back',
                 'changePassword',
+                'unlock',
                 'clearResponses',
+                'clearOtpSecret',
                 'verification',
                 'deleteUser'
             ],
             'customButtons': [
                 {
                     'name': 'custom_0',
-                    'label': 'ConfirmButton2',
-                    'description': 'My Description...'
+                    'label': 'Clone User',
+                    'description': 'Clones the current user'
+                },
+                {
+                    'name': 'custom_1',
+                    'label': 'Merge User',
+                    'description': 'Merges the current user with another user'
                 }
             ]
         });
     }
 
     getRecentVerifications(): IPromise<IRecentVerifications> {
-        return this.$q.resolve([
+        return this.simulateResponse([
             {
                 timestamp: '2017-12-06T23:19:07Z',
                 profile: 'default',
@@ -403,10 +437,34 @@ export default class HelpDeskService implements IHelpDeskService {
     }
 
     sendVerificationToken(userKey: string, choice: string): IPromise<IVerificationTokenResponse> {
-        return this.$q.resolve({ destination: 'bcarrolj@paypal.com' });
+        return this.simulateResponse({ destination: 'bcarrolj@paypal.com' });
+    }
+
+    private simulateResponse<T>(data: T): IPromise<T> {
+        let self = this;
+
+        let deferred = this.$q.defer();
+        let deferredAbort = this.$q.defer();
+
+        let timeoutPromise = this.$timeout(() => {
+            deferred.resolve(data);
+        }, SIMULATED_RESPONSE_TIME);
+
+        // To simulate an abortable promise, edit SIMULATED_RESPONSE_TIME
+        deferred.promise['_httpTimeout'] = deferredAbort;
+        deferredAbort.promise.then(() => {
+            self.$timeout.cancel(timeoutPromise);
+            deferred.resolve();
+        });
+
+        return deferred.promise;
+    }
+
+    unlockIntruder(userKey: string): IPromise<ISuccessResponse> {
+        return this.simulateResponse({ successMessage: 'Unlock successful.' });
     }
 
     validateVerificationData(userKey: string, data: any, method: string): IPromise<IVerificationStatus> {
-        return this.$q.resolve({ passed: true });
+        return this.simulateResponse({ passed: true });
     }
 }

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

@@ -35,12 +35,23 @@ const VERIFICATION_PROCESS_ACTIONS = {
 
 export interface IHelpDeskService {
     checkVerification(userKey: string): IPromise<IVerificationStatus>;
+    clearOtpSecret(userKey: string): IPromise<ISuccessResponse>;
+    clearResponses(userKey: string): IPromise<ISuccessResponse>;
+    customAction(actionName: string, userKey: string): IPromise<ISuccessResponse>;
+    deleteUser(userKey: string): IPromise<ISuccessResponse>;
     getPerson(userKey: string): IPromise<any>;
     getRecentVerifications(): IPromise<IRecentVerifications>;
     sendVerificationToken(userKey: string, choice: string): IPromise<IVerificationTokenResponse>;
+    unlockIntruder(userKey: string): IPromise<ISuccessResponse>;
     validateVerificationData(userKey: string, formData: any, tokenData: any): IPromise<IVerificationStatus>;
 }
 
+export interface IButtonInfo {
+    description: string;
+    label: string;
+    name: string;
+}
+
 export type IRecentVerifications = IRecentVerification[];
 
 type IRecentVerification = {
@@ -50,6 +61,10 @@ type IRecentVerification = {
     method: string
 }
 
+export interface ISuccessResponse {
+    successMessage: string;
+}
+
 interface IValidationStatus extends IVerificationStatus {
     verificationState: string;
 }
@@ -94,6 +109,51 @@ export default class HelpDeskService implements IHelpDeskService {
             });
     }
 
+    clearOtpSecret(userKey: string): IPromise<ISuccessResponse> {
+        let url: string = this.pwmService.getServerUrl('clearOtpSecret');
+        let data: any = { userKey: userKey };
+
+        return this.pwmService
+            .httpRequest(url, { data: data })
+            .then((result: ISuccessResponse) => {
+                return this.$q.resolve(result);
+            });
+    }
+
+    clearResponses(userKey: string): IPromise<ISuccessResponse> {
+        let url: string = this.pwmService.getServerUrl('clearResponses');
+        url += `&userKey=${userKey}`;
+
+        return this.pwmService
+            .httpRequest(url, {})
+            .then((result: ISuccessResponse) => {
+                return this.$q.resolve(result);
+            });
+    }
+
+    customAction(actionName: string, userKey: string): IPromise<ISuccessResponse> {
+        let url: string = this.pwmService.getServerUrl('executeAction');
+        url += `&name=${actionName}`;
+        let data: any = { userKey: userKey };
+
+        return this.pwmService
+            .httpRequest(url, { data: data })
+            .then((result: ISuccessResponse) => {
+                return this.$q.resolve(result);
+            });
+    }
+
+    deleteUser(userKey: string): IPromise<ISuccessResponse> {
+        let url: string = this.pwmService.getServerUrl('deleteUser');
+        url += `&userKey=${userKey}`;
+
+        return this.pwmService
+            .httpRequest(url, {})
+            .then((result: ISuccessResponse) => {
+                return this.$q.resolve(result);
+            });
+    }
+
     getPerson(userKey: string): IPromise<any> {
         let url: string = this.pwmService.getServerUrl('detail');
         let verificationState = this.localStorageService.getItem(this.localStorageService.keys.VERIFICATION_STATE);
@@ -134,6 +194,17 @@ export default class HelpDeskService implements IHelpDeskService {
             });
     }
 
+    unlockIntruder(userKey: string): IPromise<ISuccessResponse> {
+        let url: string = this.pwmService.getServerUrl('unlockIntruder');
+        url += `&userKey=${userKey}`;
+
+        return this.pwmService
+            .httpRequest(url, {})
+            .then((result: ISuccessResponse) => {
+                return this.$q.resolve(result);
+            });
+    }
+
     validateVerificationData(userKey: string, data: any, method: string): IPromise<IVerificationStatus> {
         let processAction = VERIFICATION_PROCESS_ACTIONS[method];
         let url: string = this.pwmService.getServerUrl(processAction);