syed_faheem
ServiceNow Employee
ServiceNow Employee

I have been observing this requirement from various MSPs or other clients who have multiple Service Portals implemented and would like to restrict users from accessing one or more portals (i.e. user who belong to another company or a specific group) also would like to redirect users to their authorized portal if they try to access another portal.

While there may be several possible solutions available (i.e. redirection widget in header or customizing header or page code to hide content if user is not permitted to see it), this solution doesn't involve customizing any OOB widgets and/or pages and is purely additive in nature.

This solution provides desired functionality using client side OOTB features provided by ServiceNow platform. It covers:

(a) Redirection to Customer's relevant portal

(b) Restrict any other portal which user should not be able to access

The solution involves 3 components which enable this functionality i.e.

  1. Global UI Script
  2. JS Script
  3. UI Page

These components interact with each other as per below diagram.

Service Portal Separation & Redirection - Page 1.jpeg

I would like to share steps to implement this solution and how you could modify it to fit your needs.

For this proof of concept, I would consider that each company/customer has their own portal and would like to restrict another customer from accessing it. Also, if a user tries to access another customer's portal they are redirected back to their portal.

Pre-Configuration & Data Setup:

  1. Configure Portal Record

        Since each customer has their own respective portal, we have created a custom field 'Company' on 'sp_portal' table which references to 'core_company'           table. Please see below picture for the dictionary definition.

portal_cfg.png

        2. Setup portals for testing

          Create couple of portals to use for testing, you may decide to create/use your own portals for this purpose however I have created following portals and           associated with respective companies as below:

portals.jpg

        3. Setup users for testing

        Setup couple of users to use for testing, you may decide to use any users for this purpose, however I have associated following users with respective           company records as below:

users.jpg

Step 1: Create a Global UI Script

This UI Script will reset browser variables whenever a user is logged in to the system.

Create UI Sript as below:

Name: sf.session.reset

Global: Checked

Script:

addLoadEvent(    

                                  function(){

                                  try{

                                                                      var currentUser=g_user.userID;

                                                                      var previousUser=localStorage.getItem('user');

                                                                      if(currentUser!=previousUser){

                                                                                                          localStorage.setItem('user',g_user.userID);

                                                                                                          localStorage.setItem("custUrl", "");

                                                                                                          localStorage.setItem("returnUrl","");

                                                                      }

                                                                      }catch(e){}

                                                                      });

Step 2: Create a UI Page

This UI page will redirect users to their respective Service Portal for the very first time and also set browser variables.

Create UI Page as below:

Name: portal_redirect

HTML:

<?xml version="1.0" encoding="utf-8" ?>

<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">

<script>

                                  addLoadEvent( function() {  

                                  redirect();  

});  

</script>                              

      <div >

          <table><tr>

              <td style='width: 300px; vertical-align: top;'>

                  <p><img src="./images/loading_anim4.gifx"/> Redirecting </p>                                        

                                                                          </td>

                                                                          </tr>

                                      </table>

                                    </div>        

</j:jelly>

Client Script:

function redirect(){

                                  var pageURI = localStorage.getItem('returnUrl');

                                  if(!pageURI){

                                                                      pageURI = window.location.toString();

                                  }

                                  var portal=getUserPortal();

                                  // setting browser variable

                                  localStorage.setItem("custUrl", portal);

                                  // returns true if user portal does not exists in url

                                  if(pageURI.indexOf("/"+portal+"?")==-1 ||

                                                                      pageURI.substr(pageURI.length - portal.length)!=portal){

                                                                      if(pageURI.indexOf('?')!=-1){

                                                                                                          window.location="/"+portal+pageURI.substring(pageURI.indexOf('?'));

                                                                      }

                                                                      else

                                                                                                          {

                                                                                                          window.location="/"+portal;

                                                                      }

                                  }

}

function getUserPortal(){

/* Retrieve user portal based on their company association in portal record, you can change below code to return the portal suffix based on your requirement. i.e. portal which user have access based on their group membership etc.. */

                                 

                                  // fetch user company

                                  var grComp= new GlideRecord('sys_user');

                                  grComp.get(g_user.userID);

                                  // fetch user portal

                                  //console.log(grComp.company.toString());

                                  var grPortal= new GlideRecord('sp_portal');

                                  grPortal.addQuery('u_company',grComp.company.toString());

                                  grPortal.query();

                                  grPortal.next();

                                  //console.log(grPortal.url_suffix);

                                  return grPortal.url_suffix;

}

Function getUserPortal() in above client script has all the logic around which portal user need to be redirected to or is authorized to access. Depending on your requirement you can modify script to return desired portal suffix.  

Step 3: Create a JS Script

This UI Script serves as JS Script in Service Portal Dependences and gets invoked before service portal / page is loaded.

Create UI Script as per below:

Name: sf.portal.redirect

Script:

invokeRedirector();

function invokeRedirector(){

                                  var portal=localStorage.getItem('custUrl');

                                  var pageURI = window.location.toString();

                                  var redPage="/portal_redirect.do";

                                  var custUrl;

                                 

                                  if(pageURI.indexOf('sp')==-1){

                                                                      if(!portal){

                                                                                                          localStorage.setItem("returnUrl", pageURI);

                                                                                                          window.location=redPage;

                                                                                                         

                                                                      }

                                                                      else if(pageURI.indexOf("/"+portal+"?")==-1 &&

                                                                                                          pageURI.substr(pageURI.length - portal.length)!=portal){

                                                                                                          if(pageURI.indexOf('?')!=-1){

                                                                                                                                              custUrl=portal+pageURI.substring(pageURI.indexOf('?'));

                                                                                                                                              window.location="/"+custUrl;

                                                                                                          }

                                                                                                          else

                                                                                                                                              {

                                                                                                                                              custUrl=portal;

                                                                                                                                              window.location="/"+custUrl;

                                                                                                          }

                                                                      }

                                  }

}

Step 4: Configure Dependency

  1. Navigate to 'Service Portal>Dependencies'
  2. Click on 'New' button
  3. Give it name of 'sf.portal.redirect'
  4. Enable check box 'Include on page load'
  5. For 'Portals for page load (optional)' field select all the portals on which you would like to implement this functionality
  6. Right click in the header and click 'Save'
  7. On 'JS Includes' related list, click 'New'
  8. Give a display name 'sf.portal.redirect'
  9. And in the field named 'UI Script' select UI script 'sf.portal.redirect'

After all this configuration, your form should look like below:

widget.png

  1. Navigate to Service Portal > Headers & Footers
  2. Open your header record i.e. 'Stock Header'
  3. Right click on page header and add 'Dependencies' related list
  4. On 'Dependencies' related list, click 'Edit'
  5. Select & move to right 'sf.portal.redirect' dependency and click 'Save'
  6. Repeat steps ii to v for all the headers which you may have used in portals.

After this configuration, related list section in header record should look like below:

dep.png

Testing the Solution

Test Case 1: Redirection to Correct Portal

Login/impersonate with a user who belongs to company 'Cloud Dimensions' and try to access portal of 'ACME Corporation' by navigating to url https://<instancename>/acme user will be immediately redirected to https://<instancename>/cd

Test Case 2: Redirection to Correct Portal with specific page

Login/impersonate with a user who belongs to company 'Cloud Dimensions' and try to access service catalog page of 'ACME Corporation' by navigating to url https://<instancename>/acme?id=sc_home user will be immediately redirected to service catalog page of Cloud Dimensions https://<instancename>/cd?id=sc_home

Test Case 3: Redirect to a non-restricted portal

Login/impersonate with a user who belongs to any company and try to access a portal which is not restricted i.e. https://<instancebame>/sp users will not be redirected and be able access this unrestricted portal.

Please share your feedback & details on how you implemented this solution to meet your requirement.

Thanks

Comments
Ulrich Jugl
ServiceNow Employee
ServiceNow Employee

Syed, we changed this slightly.



Instead of a reference on sp_portal my reference is on core_company, as I would have multiple companies using the same portal (customer portal), while internal users are using an internal portal.



In addition, I put the getUserPortal script into a script include and called it via AJAX. This way I don't need to ensure that every user can read the company or sp_portal tables.



Script Include: PortalSeparationAjax


Client callable: checked


Script:



var PortalSeparationAjax = Class.create();


PortalSeparationAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {



getCustURL: function() {


/* Retrieve user portal based on their portal association in company record, you can change below code to return the portal suffix based on your requirement. i.e. portal which user have access based on their group membership etc.. */


//Fallback to 'CP' in case we cannot read an answer


var answer = 'cp';



//user is a parameter:


var userID = this.getParameter('sysparm_user_id');



// fetch user


var grComp= new GlideRecord('sys_user');


grComp.addQuery('sys_id', userID);


grComp.query();


grComp.next();



// fetch user company


var grCompany= new GlideRecord('core_company');


grCompany.addQuery('sys_id', grComp.company + '');


grCompany.query();


grCompany.next();



// fetch company portal


var grPortal = new GlideRecord('sp_portal');


grPortal.addQuery('sys_id', grCompany.u_portal + '');


grPortal.query();


grPortal.next();


if (grPortal.url_suffix != '')


answer = grPortal.url_suffix + '';



return answer;


},



type: 'PortalSeparationAjax'


});




UI Page adjustments:


Name: portal_redirect


Client Script:



function redirect(){


var pageURI = localStorage.getItem('returnUrl');


if(!pageURI){


pageURI = window.location.toString();


}


var portal=getUserPortal();


// setting browser variable


localStorage.setItem("custUrl", portal);


// returns true if user portal does not exists in url


if(pageURI.indexOf("/"+portal+"?")==-1 ||


pageURI.substr(pageURI.length - portal.length)!=portal){


if(pageURI.indexOf('?')!=-1){


window.location="/"+portal+pageURI.substring(pageURI.indexOf('?'));


}


else {


window.location="/"+portal;


}


}


}



function getUserPortal(){




var ga = new GlideAjax('PortalSeparationAjax');


ga.addParam('sysparm_name', 'getCustURL');


ga.addParam('sysparm_user_id', g_user.userID);


ga.getXMLWait();



return ga.getAnswer();


}





UI Script adjustments:


Name: sf.portal.redirect / we removed a condition in the else if path, as it was leading to false behaviour if you try to hack the URL and we removed the IF statement for SP, as this is our main portal where only internal users should have access.



invokeRedirector();


function invokeRedirector(){


var portal=localStorage.getItem('custUrl');


var pageURI = window.location.toString();


var redPage="/portal_redirect.do";


var custUrl;



if(!portal){


localStorage.setItem("returnUrl", pageURI);


window.location=redPage;



}


else if(pageURI.indexOf("/"+portal+"?")==-1){


if(pageURI.indexOf('?')!=-1){


custUrl=portal+pageURI.substring(pageURI.indexOf('?'));


window.location="/"+custUrl;


}


else


{


custUrl=portal;


window.location="/"+custUrl;


}


}


}


syed_faheem
ServiceNow Employee
ServiceNow Employee

Great suggestion ulrichjugl. Thanks for contributing to this article.


sai4a7
Kilo Contributor

I've followed every step you explained, But users are not redirecting to their portals. Please help me..

Akhil Pendem
Tera Contributor

Hi Sai,

Did you find any solution?

Ulrich Jugl
ServiceNow Employee
ServiceNow Employee

Have you checked if the users have the right custURL set in their local storage in their browser? You can start by looking into this to see, if after login this field is properly filled. If not, you will need to look into the global scripts if there is an error (or if the user is not setup correctly). If yes, you will need to look if there is an issue with the redirect in your specific situation.

Kim Sullivan
Tera Guru

When you implemented this, did you ever have your users be redirected to the right portal, but then the page never loaded, just was blank?  I could tell it was the right portal, by the name in the title bar of the browser.

Sam Ogden
Tera Guru

Hi Ulrich,

Is there anyway we could amend the scripts to check for the users IP address?

We need to stop some users accessing a portal if they are trying to access it from an IP address we don't have listed?

Any help is greatly appreciated.

Thanks

Sam

Ulrich Jugl
ServiceNow Employee
ServiceNow Employee

Kim, no I haven't found that situation but mainly because the pages we were redirecting to existed in both of the portals. You would need to check if you have e.g. a page available in a portal and then maybe need to figure out what to do when it doesn't exist.

Ulrich Jugl
ServiceNow Employee
ServiceNow Employee

Not sure if this would be the right place to check for this. Instead I'd think you would want to do this during login to the portal? There are multiple places to touch if you want to add an IP check. Would it be an option to use IP Address Access plugin or do you need the instance available outside of your network?

Sam Ogden
Tera Guru

Hi Ulrich,

We are b2b so we have many portals - 1 for each of our customers.  On one of these portals we need it so our customers contacts can only access the portal whilst in there network.  Our instance we require access out of the network.

Any help on how best to tackle this is greatly appreciated.

Thanks

Sam

Ulrich Jugl
ServiceNow Employee
ServiceNow Employee

Would you have this requirement (specific IP addresses) for all of the customers or just this one? Without going into a solution directly, I could think of something of having another related table to the core_company record, which lists the allowed IP ranges.

During the session creation (here in portal_redirect) you could have another check to see if this related list is empty for a company or not. If it is empty, just go ahead and let the user access the portal. In case there are IP addresses and they don't match, you could redirect to another page on the portal which informs them, that they need to get into their VPN or whatsoever first.

Sam Ogden
Tera Guru

Hi Ulrich,

Currently it is just one of our clients (so just one portal), but potentially could be others in the future too.

Apologies, I'm not sure which part you mean for the session creation?  what would I need to change and where in order to capture the users IP address?

I had tried to add a script within user criteria to the homepage but this didn't seem to work, so thought that it might be possible this way?

Cheers

Sam

Josh Soutar
ServiceNow Employee
ServiceNow Employee

Hi Sam,

 

To better understand your current config, can you say if you've followed Syed's original solution or did you go with Ulrich's updated solution instead?

Sam Ogden
Tera Guru

Hi Josh,

I went with Ulrich's with the script include and the lookup being on the company record.

In the meantime I've also been exploring having a widget embedded in the portal header which did the check.  I got this to work, but after logging in, the homepage initially loads for a second or 2, then the redirect happens.  Also if on the page I redirect to if I show the header menu, and the user selects any of the links in the menu the widget does not re-execute so they can then navigate to other pages.  I did add page specific Css to hide the header menu, but this could be amended in the console if the person knew what they were doing.  Ideally the check would be done everytime they try to navigate anywhere which I was hoping would happen if it was in the header?  My widget is like this:

Server Script:

(function() {
  
	var ip_test = '200.225.196.97'; 
	data.ip_address = gs.getSession().getClientIP().toString();
	
	if(data.ip_address == ip_test) {
		data.message = 'Your IP Address Matches';
		
	}
	else {
		data.message = 'Incorrect IP - Access not allowed';
		data.redirect = true;
	}
	
})();

Client Script:

function($location) {
    /* widget controller */
    var c = this;

    if (c.data.redirect) {
        $location.url("/cdl?id=ip_error")
		c.server.update()
    }
}

Thanks Sam

Josh Soutar
ServiceNow Employee
ServiceNow Employee

Alright, great! So a couple things come to mind here.

 

First of all, i'd advise against using a widget in the header to handle the redirect. Due to the order in which a portal page loads, there will always be a delay before the widget loads and is triggered (as you've noted above with the homepage loading then 2 seconds later, the redirect triggering). In general, you should also avoid using CSS to hide a header as it's not really a conventional approach.

 

If you've followed Ulrich's solution, I think the most logical place to have the user IP check is within the PortalSeparationAjax script include. This is where you can add logic to get the user's IP address, what company they belong to, what portal they should be redirected to and then compare this against the range of allowed IP addresses that a user should have in order to be redirected to a specific portal.

Sam Ogden
Tera Guru

Hi Josh,

Thanks for the above.  Yes I didn't feel that this was the best solution.

I'm just not sure how to add the logic to the script include to check the current users IP address.  in the widget on the server side I was using - 

data.ip_address = gs.getSession().getClientIP().toString();

In the script include could I use:

gs.getSession(userID).getClientIP().toString();

with userID already being set in the script include? Not that used to this so any help is greatly appreciated.

Thanks

Sam

Josh Soutar
ServiceNow Employee
ServiceNow Employee

You can call gs.getSession().getClientIP().toString() directly within the PortalSeparationAjax script include. I think all you have to do here is write additional code within the getCustURL function to add the logic for getting the client's IP address, then checking this against the range of allowed IPs to access a specific portal.

WorkingHands
Tera Expert

I'm wondering if anyone every got this working WITHOUT relying on localStorage?

 

While the solution worked perfectly for me on desktop, on a mobile browser localStorage is not available and so the redirection does not work.

 

Any advice?

 

Thanks

vivian08081
Giga Expert

@syed_faheem 

 

Hi I followed step by step as you posted, but it still can't direct sp to the correct one.

 

Cloud you please help on it. Did I missing anything or cloud you please go through my instance?

 

Thank you.

vivian08081
Giga Expert

@sai4a7

I also did step by step, but it doesn't work. Have found out the solution?

Version history
Last update:
‎11-12-2017 04:11 PM
Updated by: