OpenID script samples

Note: This article is valid for Cloud solutions only.

Non-interactive login without using the user's refresh token and searching for users and group

The OpenID support has been expanded to incorporate non-interactive login without using the user's refresh token and searching for users and groups. To do this, the original login script has been renamed to script and has been expanded to allow for more than just one function.

You are allowed to make as many functions as needed in the script, but the following functions are reserved for specific purposes:

 

Function signature Description
interactive_login(token, access_token)

This is the old function to handle interactive users (e.g. a user that wants to log on to TARGIT or Anywhere). It will be called after the TARGIT Server has obtained an ID token, access token for the user logging in

  • token is the ID token decoded into a javascript object
  • access_token is the access token. It has not been decoded in any way. It is meant to be used to e.g. query extra information about the user from the Microsoft Graph API

The function should return an object like this:

{
user_name: "...",
user_id: "...",
user_groups: [],
email: "...",
roles: ...,
replace_roles: ...,
language: "..."
}
  • user_name is the user's name. This property is required
  • user_id is the user's id. This property is optional. If it is not present, the server will use the currently configured security model to look up the user ID (e.g. using the Active Directory if Windows security is enabled) using the user_name to look for the user. If user_id is specified and Prefix user IDs is enabled on the identity provider, the user_id will  be prefixed with the identity provider ID + backslash to distinguish the users from the "internal" users (from Active Directory/standard users)
  • user_groups is an array of groups the user is member of. A group can either be represented just by its ID or as an object like this: 

 {
id: "...",
name: "...",
display_name: "..."
}

The name and display_name properties are only for display purposes in the editor. It is only the group id that is being used to evaluate group membership in roles/rights. You can freely mix the short-form and object form: 

 [ "id1", { id: "id2", name: "group2", display_name: "group2 display name" }, "id3" ]
  • email is the user's email. It is required if you want to use the subscription feature.
  • roles and replace_roles are described here: Parameterized/Parametric Roles
  • language can be used to override the user's language. Currently, the Windows client will not obey this in the translations for the menu and dialogs - only for metadata and queries. If you do not want to override the language, simply do not include the property in the return statement.
noninteractive_login(username)

This function is used when a user is logged on non-interactively - i.e. when running a scheduled job for a user. 

  • username is the name of the user logging in (i.e. running a scheduled job)

The function should return an object in the same format as interactive_login(). It should retrieve the user's information using e.g. the Microsoft Graph API (if using Azure AD) or something similar

user_search(search_string)

This function is used when editing roles/rights to search for users.

  • search_string is what the user is searching for. It can be empty and should behave appropriately (maybe just returning all the users)

The function should return an array of items with the same format as the items in user_groups from interactive_login/noninteractive_login.

The id will be prefixed with the identity provider ID + backslash if PrefixUserIDs is enabled on the identity provider.

group_search(search_string)

This function is used when editing roles/rights to search for groups.

  • search_string is what the user is searching for. It can be empty and should behave appropriately (maybe just returning all the groups)

The function should return an array of items with the same format as the items in user_groups from interactive_login/noninteractive_login.

 

The following global variables are defined when any of the functions are invoked:

Name Description
CLIENT_ID The client id for the identity provider
CLIENT_SECRET The client secret
TOKEN_ENDPOINT The token endpoint for the identity provider
LoginApp What application is trying to log on (Windows, Anywhere, MS) - only applicable for interactive_login/noninteractive_login
CLOUD_TENANTID Cloud only: The tenant ID
CLOUD_STAGEID Cloud only: The stage ID

 

The following objects are declared:

Name Description
lookup

Used to query users/groups using the selected security model. It will e.g. query the Active Directory if the security model is set to Windows.

Methods:

  • getUserID(userName) - returns the user ID for the named user
  • getGroupID(groupName) - returns the group ID for the named group
  • getGroupsForUser(userID) - returns the groups the user is member of
  • getUserFromADDetailedSID(userSID) - returns detailed information from the Active Directory about the user. Only works for Windows Security
HTTP

Used to issue REST queries

Methods:

  • fetch(url, options) - issues a HTTP request to the uri using the provided options. See HTTPFetchOptions for more information about options. Returns a string containing the body of the response. Throws an exception if the status code is not in the 200-399 range
  • login_client_credentials(token_endpoint, client_id, client_secret, scope, ignore_cache = false) - Logs on to the identity provider using client credentials with client id and secret. Returns the access token obtained. It will cache the access token to reduce the load on the token endpoint. You can bypass the cache by calling it with the ignore_cache parameter set to true
  • login_refreshtoken(token_endpoint, client_id, client_secret, refresh_token, scope, ignore_cache = false) - Logs on to the identity provider using a refresh token. Returns the access token obtained. It will cache the access token to reduce the load on the token endpoint. You can bypass the cache by calling it with the ignore_cache parameter set to true
  • url_encode(url, path_parameters, query_parameters) - encodes an url using the named path parameters and query parameters. E.g. calling HTTP.url_encode("https://targit.com/{id}/fetch", { id: "test" }, { query: "hello world" }) will return https://targit.com/test/fetch?query=hello%20world. 
HTTPFetchOptions

Options object for HTTP.fetch()

Properties:

  • method - the HTTP method (GET, POST, DELETE, etc.) used. Defaults to GET
  • headers - object containing the HTTP request headers
  • body - the request body for POST, PUT, etc.
Cache

Simple cache for the scripts. Remember that the cache is global for all users and identity providers, so use some clever cache key schemes to avoid "pollution" from other users/providers

Methods:

  • get(cache_key) - retrieves the cache entry with key cache_key. If it does not exist, it returns undefined
  • set(cache_key, value, timeout = -1) - stores the value under cache_key for timeout seconds. If timeout = -1, the cache item will not time out. If value is undefined, it removes the cache item instead (same as remove(cache_key))
  • remove(cache_key) - removes the cache item
console

Class to generate debugging output.

Methods:

  • log(value) - logs the value to be displayed in the Manage script dialog
JSON

Class to encode/decode JSON

Methods:

  • parse(json) - parses the JSON and returns the parsed value
  • stringify(value) - returns the JSON encoded value

 

This example script is suitable on an Azure AD tenant where the local AD is synchronized with Azure AD and you want to use the local security identifiers and account names. If you have a Azure AD tenant without any synchronization you would want to use some of the other properties of users and groups in the Graph API (like id and userPrincipalName for instance).

Example script for Azure AD using Microsoft Graph API to get user information:

var domain_prefix = "DomainName\\";
var upn_suffix = "@YourDomain.com";

async function getGroups(accessToken, userID) {
var fetchOptions = { headers: { Authorization: "Bearer " + accessToken } };
var groups = [];

var nextUrl = HTTP.url_encode("https://graph.microsoft.com/v1.0/users/{user}/memberOf", { user: userID }, { "$select": "displayName,onPremisesSamAccountName,onPremisesSecurityIdentifier" });
do
{
var data = JSON.parse(await HTTP.fetch(nextUrl, fetchOptions));
groups.push(...data.value.map(g => ({ id: g.onPremisesSecurityIdentifier, name: domain_prefix + g.onPremisesSamAccountName, display_name: g.displayName })));
nextUrl = data["@odata.nextLink"];
} while (nextUrl)
return groups;
}

async function interactive_login(token) {
var usernameparts = token["preferred_username"].split("@");
var accessToken = await HTTP.login_client_credentials(TOKEN_ENDPOINT, CLIENT_ID, CLIENT_SECRET, "https://graph.microsoft.com/.default");

return {
user_name: domain_prefix + usernameparts[0],
user_id: token.oid,
email: token.email,
user_groups: await getGroups(accessToken, token.oid)
};
}

async function noninteractive_login(username) {
var parts = username.split("\\");
var upn = parts[parts.length-1] + upn_suffix;
var accessToken = await HTTP.login_client_credentials(TOKEN_ENDPOINT, CLIENT_ID, CLIENT_SECRET, "https://graph.microsoft.com/.default");
var userUrl = HTTP.url_encode("https://graph.microsoft.com/v1.0/users/{user}", { user: upn }, { "$select": "id,onPremisesSamAccountName,mail" });

var fetchOptions = { headers: { Authorization: "Bearer " + accessToken } };
var userData = JSON.parse(await HTTP.fetch(userUrl, fetchOptions));
return {
user_name: domain_prefix + userData.onPremisesSamAccountName,
user_id: userData.id,
email: userData.mail,
user_groups: await getGroups(accessToken, userData.id)
};
}

function quote(s) {
return '"' + String(s).replace('"', '\\"') + '"';
}

async function user_search(searchString) {
var accessToken = await HTTP.login_client_credentials(TOKEN_ENDPOINT, CLIENT_ID, CLIENT_SECRET, "https://graph.microsoft.com/.default");
var params = { "$select": "id,onPremisesSamAccountName,displayName" }
if (searchString) {
params["$search"] = quote('displayName:' + searchString);
}
var nextUrl = HTTP.url_encode("https://graph.microsoft.com/v1.0/users", { }, params);
var fetchOptions = { headers: { Authorization: "Bearer " + accessToken, ConsistencyLevel: "eventual" } };
var result = [];

do {
var users = JSON.parse(await HTTP.fetch(nextUrl, fetchOptions));
result.push(...users.value.filter(u => u.onPremisesSamAccountName).map(u => ({ id: u.id, name: domain_prefix + u.onPremisesSamAccountName, display_name: u.displayName })));
nextUrl = users["@odata.nextLink"];
} while (nextUrl);
return result;
}

async function group_search(searchString) {
var accessToken = await HTTP.login_client_credentials(TOKEN_ENDPOINT, CLIENT_ID, CLIENT_SECRET, "https://graph.microsoft.com/.default");
var params = { "$select": "onPremisesSecurityIdentifier,onPremisesSamAccountName,displayName" }
if (searchString) {
params["$search"] = quote('displayName:' + searchString);
}
var nextUrl = HTTP.url_encode("https://graph.microsoft.com/v1.0/groups", { }, params);
var fetchOptions = { headers: { Authorization: "Bearer " + accessToken, ConsistencyLevel: "eventual" } };
var result = [];

do {
var groups = JSON.parse(await HTTP.fetch(nextUrl, fetchOptions));
result.push(...groups.value.filter(g => g.onPremisesSamAccountName).map(g => ({ id: g.onPremisesSecurityIdentifier, name: domain_prefix + g.onPremisesSamAccountName, display_name: g.displayName })));
nextUrl = groups["@odata.nextLink"];
} while(nextUrl);
return result;
}

To use this script on your Azure AD tenant, you need to enable User.Read.All and Group.Read.All permissions on the app registration, so the app itself (using client ID and client secret) can get that information.

image2023-2-22_7-48-41.png

Was this article helpful?
1 out of 1 found this helpful

Comments

0 comments

Please sign in to leave a comment.