One of my clients wanted to allow an outside reporting team to utilize an ODBC driver to pull specific data out of ServiceNow. However, due to the size of data that they would potentially pull, the IT folks wanted to restrict the user that they gave to that team such that they could only authentication for the ODBC driver query during a specific time of the day.
If this were something that was going to be used by a lot of customers, I would approach this problem by using Common Schedules as built out in the instance. I would then tailor it to basic authentication blackout periods. However, this request was more of a query rather than something that I am certain will be implemented. Since this is not a very oft requested feature, I decided to make it a simple implementation and use my own interface and logic to make this work.
The User Interface
For this feature, I created a table called “Basic Auth Time Restrictions”. I created a module for this table and placed it in the “System Security” application within the ServiceNow instance.
Once you browse to that module, you will get a list that you can populate with user records and login windows for Basic Login. Remember, the purpose of the login restrictions is to affect services and programs during authentication, not people coming into the instance via a browser.
The following is a screenshot of the list that is available in the Basic Auth Time Restriction module:
In order to restrict basic authentication for a user, simply create a “Basic Auth Time Restriction” record. When you click the “New” button, you will see the following form:
Let’s talk about each of the fields:
Schedule Active: When this checked, this login window will go into affect. When unchecked, the system ignores this window.
User: Specify a local user in the ServiceNow instance. If a user is not listed in the restrictions list, we assume that there are no restrictions for the user and they can login freely at any time.
Begin Time: The beginning time (in your current timezone) of the valid login window.
End Time: The ending time for the valid login window.
If the begin time is later than the end time, the system will assume that your times span two days. (eg: 21:00:00 – 03:00:00 is interpreted that the user can login from 9PM today through 3AM tomorrow).
A user can have multiple time restriction records. During authentication, the system will check all of the user’s restriction records to see if any of them contain a window that fits the current time. If it does, the user will be authenticated.
The Gorey Details
In order to apply these Basic Authentication login windows, I had to modify the BasicAuth script include in my instance. I deactivated the original script include and copied the script to a new BasicAuth script include.
I then added a new function to the script include called “timeRestricted(userName)”. This function takes in the userName that is being used for authentication. It then looks up the user name to get a user record in the system. Once it has the user record, it queries our “Basic Auth Time Restriction” table to find any restriction records for the given user. If no records exist in the table for the user, then it continues the authentication process.
If there are ACTIVE records in the restrictions table, the script will go through each record to see if the current time falls within the begin-end time spans. If it finds a valid window, it will allow the user to authenticate. Otherwise, it will deny user authentication with a log statement indicating that there was a time restriction for the user.
To help you understand how this was done, I have included the script here:
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | var BasicAuth = Class.create(); BasicAuth.prototype = { initialize: function (request, response, auth_type, auth_value) { this.request = request; this.response = response; this.auth_type = auth_type; this.auth_value = auth_value; }, getAuthorized: function () { var up = Packages.com.glide.util.StringUtil.base64Decode(this.auth_value); var split = up.indexOf(":"); if (split == -1) { gs.log("Basic authentication not well formed"); return null; } // locate user and impersonate var userName = up.substring(0, split); var password = up.substring(split + 1); if (!Packages.com.glide.sys.User.authenticate(userName, password)) { gs.log("Basic authentication failed for user: " + userName); return null; } this.updateLastLogin(userName); /* * Custom code inserted to handle time restrictions */ if (this.timeRestricted(userName)) { gs.log("User: " + userName + " is time restricted. Login Denied."); return null; } /* * End of custom Code insert block */ // user is authenticated, so return it... return userName; }, updateLastLogin: function (userName) { if ('true' != gs.getProperty("glide.basicauth.update_last_login_time", "false")) { return; } var user = new GlideRecord("sys_user"); user.addQuery("user_name", userName); user.query(); if (user.next()) { user.last_login_time = new GlideDateTime(); user.update(); } }, /* * Custom function inserted to handle Basic Auth Time Restrictions */ timeRestricted: function (userName) { gs.log("Checking for a time restriction on " + userName); var _EARLIER = -1; var _LATER = 1; var _SAME = 0; var user = new GlideRecord("sys_user"); user.addQuery("user_name", userName); user.query(); if (user.next()) { var restriction = new GlideRecord("u_basic_auth_time_restrictions"); restriction.addQuery("u_schedule_active", "true") restriction.addQuery("u_user", user.sys_id); restriction.query(); var restrictionsFound = 0; while (restriction.next()) { restrictionsFound++; var currentGmtTime = new GlideDateTime(); currentGmtTime = new GlideDateTime(currentGmtTime.getLocalTime()); var begin = new GlideDateTime(restriction.u_begin_time); begin = new GlideDateTime(begin.getLocalTime()); var end = new GlideDateTime(restriction.u_end_time); end = new GlideDateTime(end.getLocalTime()); //Check for exact same time scenario if (currentGmtTime.compareTo(begin) == _SAME) { return false; } if (currentGmtTime.compareTo(end) == _SAME) { return false; } //Handle case where begin and end are times within the same day if (end.compareTo(begin) == _LATER) { if ((currentGmtTime.compareTo(begin) == _LATER) && (currentGmtTime.compareTo(end) == _EARLIER)) { return false; } } //Handle case where end is before the begin time - this means they span two days if (end.compareTo(begin) == _EARLIER) { if ((currentGmtTime.compareTo(begin) == _LATER) || (currentGmtTime.compareTo(end) == _EARLIER)) { return false; } } } if (restrictionsFound == 0) { //This user has no restriction records return false; } } //Could not find any openings for this time - must be restricted return true; } /* * End of custom Code insert block */ } |
Download the Update Set
I have provided an update set for you to download in order to use this feature for yourself. If you do choose to use this, let me know how it works for you.
John, have ever saw a configuration that would pop a warning screen to let a user know there session is about to expire and allow them to restart the inactivity clock?
@Scott, I don’t have one of my own, but I have heard of some consultants that have created scripts doing what you are asking about. I would reach out to Andrew Kincaid as I believe he may have written a similar script.
Excellent, works well in Dev so far, no trouble with the update set.
This might be a great solution for preventing users of the ODBC driver hitting the DB with long complicated queries during business hours.
Do you know… if the schedule kicks in and the user account is currently logged in, will the user get kick out?
@Anna. That is a good idea as well. On your question, no, this only applies during a login. It does not terminate an active session. You would have to look at another solution for that (maybe a scheduled job that runs every hour or so).