Service Portal – Impersonate Dialog

Recently, I have been involved heavily in building out our Service Portal at my company as we are going live with it. In the process of doing so, I need to be able to impersonate ESS users in the Service Portal similar to that of the Platform UI. Service Portal does not have that out-of-box so I did some research. However, most of what I found either requires me to clone the stock header or having to add a widget to the page. I wanted to find a better way to implement this solution that does NOT require cloning or adding a widget to the page and so, I started working on a solution that would fit my needs better. I would like to thank mhedtke24 for the jQuery selector in this awesome post and Shahid Shah for the $http API path in this cool widget on ServiceNow share, as well as jpierce.cerna for the clever way to remove the spModal footer. The update set is available on ServiceNow Share and the link is at the end of this post, without further ado, let’s get started!

I needed the ability to “inject” my own menu item to the Service Portal stock menu. I can do this by creating my own angular provider. However, I still need to figure out a way to somehow call/invoke it without cloning the stock header. I was able to achieve this by adding the angular provider to the menuTemplate that is called by the header menu, I added an $index check so that the provider is only called once:
MenuTemplate:

I created the provider mentioned above and added it to the “Angular Provider” related list on the Stock Header form. Note that the name of the provider is camelCase and is translated to sp-impersonate-dialog

Angular Provider (Directive):
Type: Directive

Name: spImpersonateDialog
Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function spImpersonateDialog(spModal,glideUserSession,$http,$rootScope,$log){ //inject dependencies here, and use in the link function
  return {
    restrict: 'E', // 'A'= attribute, 'E' = element, 'C' = Class
    link: function(scope){
      if(scope.user.logged_in && !$rootScope.isDirectiveLoaded){
        $rootScope.isDirectiveLoaded = true;

        $http.get('/api/now/ui/impersonate/recent').success(function(response) {
          $rootScope.recentImpersonatedUsers = response.result;

          glideUserSession.loadCurrentUser().then(function(currentUser) {
            var index = response.result.map(function(user){return user.user_sys_id;}).indexOf(currentUser.userID);
            if(currentUser.hasRole('impersonator') || index != -1){
              $('#sp-nav-bar > ul:nth-child(2) > li.hidden-xs.dropdown > ul > li:nth-child(1)').after('<li ng-if="::!(isViewNative|| isViewNativeTable)" style="cursor: pointer" role="presentation"><a role="menuitem" tabindex="-1" id="impersonation">${Impersonate User}</a></li>');
              $('#sp-nav-bar > ul:nth-child(2) > li:nth-child(1)').after('<li class="visible-xs-block" style="cursor: pointer"><a role="menuitem" id="impersonation">${Impersonate User}</a></li>');
              $("#impersonation").on('click', function() {
                spModal.open({
                  title: "Impersonate User",
                  widget: "impersonate-dialog",
                  buttons: [],
                  widgetInput: {}
                });
              });
            }
          });
        }).error(function(response) {
          $log.error("Error getting recent impersonations", response.error);
        });
      }
    },
  };
}

Here, I used three dependencies:
– spModal: to load the impersonation widget.
– glideUserSession: get access to the current user object, equivalent to g_user.
– $cookies: this allows me to add a custom cookie to track whether or not impersonation is taking place.

In the link function, I perform the following checks/actions:
– If the user is currently logged in, check if this user has a “impersonator” role or if impersonation is taking place. If so, inject the additional menu item to the list after “Profile” and add a click event to it. When clicked, it will open the impersonate-dialog widget.
– If user is not logged in, remove the cookies. No errors if the cookie does not exist.
The rest is standard format for AngularJS provider, you can find more information here. Next, let’s move on to the widget.

Impersonate Dialog Widget:
Name: Impersonate Dialog
ID: impersonate-dialog
Body HTML template:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<div class="container-fluid no-padding">
  <div class="row">
    <!--sn-record-picker-->
    <div id="record-picker" ng-class="data.isImpersonating ? 'col-xs-9' : 'col-xs-12'">
      <sn-record-picker placeholder="Search for user" field="selectedUser" table="'sys_user'" display-fields="'user_name'" value-field="'sys_id'" search-fields="'name,user_name'" page-size="100" on-change="impersonate(selectedUser.value,'start')"></sn-record-picker>
    </div>
    <!--unImpersonate-button-->
    <div id="unImpersonate-button" class="col-xs-3" ng-if="data.isImpersonating">
      <button class="btn btn-danger btn-block" ng-click="impersonate(data.realSysId, 'end')">${Unimpersonate}</button>
    </div>
  </div>
</div>
<!--recent-users-list-container-->
<div class="container-fluid custom-padding">
  <div class="panel panel-default"  id="recent-users-list">
    <div class="panel-heading">
      <h3 class="panel-title">Recent Impersonations</h3>
    </div>
    <ul class="list-group">
      <!--users-list, $root.recentImpersonatedUsers contains the list of recently impersonated users, populated by our custom directive spImpersonateDialog-->
      <li class="list-group-item highlight" ng-click="impersonate(user.user_sys_id, 'start')" id="recentImpersonatedUsers" ng-if="$root.recentImpersonatedUsers.length > 0" ng-repeat="user in $root.recentImpersonatedUsers">
        {{user.user_display_value}}
      </li>
    </ul>
  </div>
</div>

In this HTML template, I use the OOB Service Portal record picker for user selection. I then check to see if impersonation is taking place to whether or not display the unimpersonate button. These two elements are displayed side-by-side. Under them is the list of recently impersonated users.

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.btn-danger{
  padding: 5px 0px;
  border: 1px solid transparent;
  border-color: #666;
  white-space: normal !important;
}
.no-padding{
  padding: 0px;
}
.custom-padding{
  padding: 15px 0px 0px 0px;
}

.highlight{
  &:hover{
    color: #2e2e2e;
    background-color: #f5f5f5;
  }
}

Server Script:

1
2
3
4
5
6
7
8
9
10
11
12
(function() {
    /*****************************************************************************
     Impersonation check:
     gs.getImpersonatingUserName():
        - Returns your username if impersonation is taking place, null otherwise
    ******************************************************************************/

    if(gs.getImpersonatingUserName() != null){
        data.isImpersonating = true;
        data.realSysId = gs.getUser().getUserByID(gs.getImpersonatingUserName()).getID();
    }
   
})();

gs.getImpersonatingUserName() will return null (if no impersonation is taking place), otherwise returns the user_id of the impersonating user.
Client controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function($scope,$window,$http,$log) {
    /* widget controller */
    var c = this;
    angular.element('.modal-footer').css({display:'none'});

    /****************************************************
    Execute REST API call to start/end user impersonation
    @params:
       sys_id: sys_id of the soon-to-be (un)impersonated user
    *****************************************************/

    $scope.impersonate = function(sys_id,action){
        $http.post('/api/now/ui/impersonate/' + sys_id, {}).success(function() {
            $window.location.reload();

        }).error(function(response) {  
            if (response.error) {  
                $scope.show_error = true;  
                $scope.error = response.error;  
                $log.error("Impersonation failed", response.error);  
            }  
        });
    };
}

I did not like the empty footer presented by the spModal function so

1
angular.element('.modal-footer').css({display: 'none'});

was used for that purpose. To impersonationate a user from the Service Portal, you can send an HTTP Post to the API, include the sys_id of the user you want to impersonate as the last parameter path. I then set the impersonating flag to either true or false base on whether or not I am starting/ending the impersonation.
That’s it! Now you know how to impersonate a user from the Service Portal on every page without cloning the stock header or insert a widget into a page. I hope you find this post helpful and thank you for taking time to read through it. Until next time, take care!
Click to download: Service Portal – Impersonate Dialog

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments