Script library
Introduction
Safewhere Identify has many scripting extensible points where you can add custom business logics to Identify runtime pipeline using C# scripts. Problems arise when you have many resources, e.g. multiple connections, and you need to configure the same script for all of them. As a consequence, if you want to make a small change to your script, you will need to update it for all connections.
Using scripts is also a flexible approach for customizing business logic for Interceptor
and Generic provider
without the need to redeploy DLLs across a single or multiple nodes after each upgrade.
Scripting library is a new way to manage your scripts. You can define your scripts on the Scripting library page. To use them, you can then add the name of a script to a scripting setting. You can see that the new scripting library feature is very similar to how Scripting claims transformation works.
Script library page
Script library page is a place to manage all your scripts.
A script has the following properties:
Name: name of a script
Script type: please see section Script type
Script: a script is written in C# language (version 8.0) and compiled against the .NET framework 4.8
Support functions by placing them in a predefined #region (
#region additional methods
---#endregion additional methods
)Some script types such as Generic provider or interceptors are full class scripts which means you must define a class for the script.
Additional assembly references: please see the Additional assembly references section.
Additional namespaces: please see the Additional namespaces section.
Validation: when saving a script, the system will validate whether the script has correct syntax and structure, and then try to compile the script.
Script types
There are totally 23 supported script types grouped into 9 different categories:
Each script types will have different input data types. Below is the specification for each type of script:
Validate authentication context class that is sent via an AuthnRequest from a SAML Service Provider
Script usage
You can use a script of this type to customize validation of an authentication context class that is sent via an AuthnRequest from a Service Provider
Script input parameters
Name | Type |
---|---|
authnRequest | AuthnRequest |
httpContext | HttpContextBase |
samlAuthnRequestInformation | SamlAuthnRequestInformation |
configSection | SamlSPMetadataConfigurationSection |
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
Example scripts
- Validate the authentication context comparison
if (authnRequest.RequestedAuthnContext != null && authnRequest.RequestedAuthnContext.Comparison != AuthnContextComparison.Minimum)
{
SystemLogger.Instance.WriteDebug("Write some information for debugging...");
Func<string> errorMessageFunc = () => "Write a detailed error here to throw an validation exception";
throw new SamlSignOnValidationException(errorMessageFunc) { ResponseErrorCode = StatusCodes.Requester };
}
- Assign an authentication context class
if (requestedAuthenticationContextModel.MinimumNeededAuthnContext == "https://data.gov.dk/concept/core/nsis/loa/Low")
{
requestedAuthenticationContextModel.Comparison = AuthenticationContextComparison.Minimum;
requestedAuthenticationContextModel.MinimumNeededAuthnContext = "https://data.gov.dk/concept/core/nsis/loa/Substantial";
}
Validate authentication context class that is returned from an upstream SAML Identity Provider
Script usage
You can use a script of this type to customize validation of authentication context class that is returned from an Identity Provider
Script input parameters
Name | Type |
---|---|
assertion | Saml2Assertion |
httpContext | HttpContextBase |
samlResponseRequestInformation | SamlResponseRequestInformation |
authConfig | SamlSignOnConfigurationSection |
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
passiveProtocolContext | PassiveProtocolContext |
Example scripts
- Validate duplicated security levels
var attributes = assertion.Statements.OfType<Saml2AttributeStatement>().SelectMany(x => x.Attributes);
if (attributes.Any(a => a.Name.Equals("dk:gov:saml:attribute:AssuranceLevel", StringComparison.OrdinalIgnoreCase)) &&
attributes.Any(a => a.Name.Equals("https://data.gov.dk/concept/core/nsis/loa", StringComparison.OrdinalIgnoreCase)))
{
throw new SamlSignOnValidationException(() => "Duplicated security level.")
{
ResponseErrorCode = StatusCodes.Responder,
ResponseSubErrorCode = StatusCodes.NoAuthnContext
};
}
- Validate the received security level
var claims = assertion.Statements.OfType<Saml2AttributeStatement>().SelectMany(x => x.Attributes)
.Where(x => x.Name.Equals("https://data.gov.dk/concept/core/nsis/loa", StringComparison.OrdinalIgnoreCase)).ToList();
if (!claims.Any())
{
throw new SamlSignOnValidationException(() => "Security level is not found.")
{
ResponseErrorCode = StatusCodes.Responder,
ResponseSubErrorCode = StatusCodes.NoAuthnContext
};
}
var assuranceLevel = claims[0].Values[0];
if(assuranceLevel == "Low")
{
throw new SamlSignOnValidationException(() => "'Low' security level is not allowed.")
{
ResponseErrorCode = StatusCodes.Responder,
ResponseSubErrorCode = StatusCodes.NoAuthnContext
};
}
Customize authentication context class that is sent via an AuthnRequest to an upstream SAML Identity Provider
Script usage
You can use a script of this type to customize authentication context class mapping from a value sent by a Service Provider to a value that will be sent to an Identity Provider. The script is configured for the Identity Provider.
Script input parameters
Name | Type |
---|---|
authnRequest | AuthnRequest |
httpContext | HttpContextBase |
samlAuthnRequestInformation | SamlAuthnRequestInformation |
logOnConfig | SamlSignOnConfigurationSection |
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
Example scripts
- Set custom values for requested authentication context method class
var sessionLoginContext = samlAuthnRequestInformation.LoginContext;
if (authnRequest.RequestedAuthnContext == null)
{
authnRequest.RequestedAuthnContext = new RequestedAuthnContext();
}
if (sessionLoginContext.TwoFactorContextModel.UseTwoFactor)
{
string customAssuranceLevel = requestedAuthenticationContextModel.StepUpAuthnContext;
SystemLogger.Instance.WriteDebug("Set assuranceLevel to {0}.", customAssuranceLevel);
authnRequest.RequestedAuthnContext.Comparison = AuthnContextComparison.Minimum;
authnRequest.RequestedAuthnContext.AuthnContextRefs.Add(new Uri(customAssuranceLevel));
authnRequest.RequestedAuthnContext.AuthnContextRefType = AuthnContextRef.AuthnContextClassRef;
}
Customize step-up behavior of a SAML Service Provider
Script usage
You can use a script of this type to customize step-up behaviors of a SAML Service Provider
Script input parameters
Name | Type |
---|---|
authnRequest | AuthnRequest |
httpContext | HttpContextBase |
samlAuthnRequestInformation | SamlAuthnRequestInformation |
configSection | SamlSPMetadataConfigurationSection |
reusableContext | PassiveProtocolContext |
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
supportsScope | bool |
Example scripts
- Set Step-up behavior based on the current context
if (reusableContext == null && requestedAuthenticationContextModel.AuthnRequestHasRequestedAuthnContext)
{
SystemLogger.Instance.WriteDebug("AuthnRequestSignOnRequestArrivedObserver: reusableContext is null. Set IsForcingAuthnDueToScopingOrStepUp = true");
temporaryProtocolContext.IsForcingAuthnDueToStepUp = true;
}
Select which Identity Providers a SAML Service Provider can use (connection dependency customization)
Script usage
You can use a script of this type to specify what Identity Providers that a Service Provider can use. This script overrides the default behavior which uses the connection dependency list.
Script input parameters
Name | Type |
---|---|
connWithMethodClasses | IList<AuthenticationConnectionWithMethodClass> |
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
httpContext | HttpContextBase |
loginContext | SessionLoginContext |
Return value
This scripting requires to return a list of AuthenticationConnection
.
Example scripts
- Filler out Identity providers by authentication context method class
string requestedAssuranceLevel = requestedAuthenticationContextModel.MinimumNeededAuthnContext;
List<AuthenticationConnection> matchConditionConnections = new List<AuthenticationConnection>();
foreach (AuthenticationConnectionWithMethodClass connectionWithMethodClass in connWithMethodClasses)
{
SystemLogger.Instance.WriteDebug(string.Format("Dependent Authentication Connection - {0}: {1}", connectionWithMethodClass.EntityIdentifier, connectionWithMethodClass.MethodClass));
if (requestedAssuranceLevel == connectionWithMethodClass.MethodClass)
{
matchConditionConnections.Add(connectionWithMethodClass.AuthenticationConnection);
}
}
return matchConditionConnections;
Customize second factor authentication policy script
Script usage
You can use a script of this type to customize the second factor policy of an Identity Provider.
Script input parameters
Name | Type |
---|---|
Rules | PolicyRules |
Here is the PolicyRules
class:
You can learn more about Policy script here
Example scripts
- Apply the policy that requires the first factor Assurance level
{
Rules.ApplyExpression(EvaluateAssuranceLevel);
}
#region additional methods
public bool EvaluateAssuranceLevel(RuleContext ruleContext)
{
var assuranceLevel = ruleContext.FirstFactorPrincipal.Claims.FirstOrDefault(c => c.Type == "dk:gov:saml:attribute:AssuranceLevel");
return assuranceLevel != null;
}
#endregion additional methods
Note that #region additional methods
and #endregion additional methods
are required when you need to split your script into smaller functions.
Policy that specifies whether a user is allowed to register an MFA method
Script usage
You can use a script of this type to to control the conditions in which your users can register their second factor methods and log on.
Script input parameters
Name | Type |
---|---|
Rules | PolicyRules |
Example scripts
Check out the guide on Conditional access policy.
Policy that specifies whether a token can be issued for a specific login
Script usage
You can use a script of this type to customize whether a token can be issued for a specific login.
Script input parameters
Name | Type |
---|---|
Rules | PolicyRules |
Example scripts
Check out the guide on How to write Token issuance access policy scripts
Policy that specifies whether an authentication request should be processed or rejected
Script usage
You can use a script of this type to customize whether an authentication request should be processed or rejected.
Script input parameters
Name | Type |
---|---|
Rules | PolicyRules |
Example scripts
Check out the guide on How to write authentication requests access policy scripts
Claims transformations
Script usage
This script type is similar to the existing Scripting claims transformation but has more advanced features such as additional namespaces and references. After defining a script, you can use it for a Scripting claims transformation.
Script input parameters
Name | Type |
---|---|
claimsPrincipal | ClaimsPrincipal |
claimDefinitionRepository | ClaimDefinitionRepository |
nameIdService | INameIdService |
httpContext | HttpContextBase |
claimTransformationPipelineContext | IClaimTransformationPipelineContext |
Example scripts
- An example of script claims transformation to manipulate claims on the
claimsPrincipal
var requestInformation = (RequestInformation)claimTransformationPipelineContext.RequestInformation;
var temporaryProtocolContext = requestInformation.LoginContext;
if (temporaryProtocolContext.TwoFactorContextModel.UseTwoFactor)
{
var uuidClaim = claimsPrincipal.FindFirst("https://data.gov.dk/model/core/eid/professional/uuid/persistent");
if (uuidClaim != null)
{
Issue("i-want-this-type", uuidClaim.Value);
Remove("https://data.gov.dk/model/core/eid/professional/uuid/persistent");
}
}
Check out the Scripting claims transformation for more information.
Home realm discovery
Script usage
This script type is similar to the existing Scripting HRD but has more advanced features such as additional namespaces and references. After defining a script, you can use it for a Scripting HRD.
Script input parameters
Name | Type |
---|---|
controllerContext | ControllerContext |
protocolConnection | IProtocolConnection |
authenticationConnections | IList<IAuthenticationConnection> |
loginContext | SessionLoginContext |
Return value
This kind of script requires to return an ASP.NET ActionResult
Example scripts
- A simple script to show an error page if the HRD can not find an expected Identity provider
if (authenticationConnections.Count != 1 && !string.IsNullOrEmpty(controllerContext.HttpContext.Request.Params.Get(WhrParameter.ParameterName)))
{
var result = new ViewResult
{
ViewName = "HRDNoIdentityProviderDetermined",
ViewData = {
Model = new HRDNoIdentityProviderDeterminedViewModel
{
CustomTitle = string.Empty,
CustomMessage = string.Empty
}
}
};
return result;
}
return null;
Check out the Scripting home realm discovery rule for more information.
Customize an AuthnRequest that is sent to an upstream SAML Identity Provider
Script usage
You can use a script of this type to customize the AuthnRequest before sending it to the upstream Identity Provider.
Script input parameters
Name | Type |
---|---|
authnRequest | AuthnRequest |
httpContext | HttpContextBase |
endpointContext | IEndpointContext |
logOnConfig | SamlSignOnConfigurationSection |
samlAuthnRequestInformation | SamlAuthnRequestInformation |
Example scripts
Check out the guide on How to customize AuthnRequest by scripting for examples.
Map authentication context method class of the second factor to a desired value
Script usage
You can use a script of this type to customize authentication context method class of the second factor to a desired value. The script is configured for the Identity Provider.
Script input parameters
Name | Type |
---|---|
claimsPrincipal | ClaimsPrincipal |
runtimeOptions | RuntimeOptions |
temporaryProtocolContext | SessionLoginContext |
Example scripts
- Set custom second factor authentication context method class to
claimsPrincipal
claimsPrincipal.SetSecondFactorAuthenticationContextMethodClass("https://data.gov.dk/concept/core/nsis/loa/Substantial");
claimsPrincipal.SetComparableSecondFactorAuthenticationContextClass(3);
Select second factor methods that users can use
Script usage
You can use a script of this type to customize second factor methods that users can use. The script is configured for OTP authentication connection.
Script input parameters
Name | Type |
---|---|
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
context | ControllerContext |
otpOptions | OtpOptions |
orderOfFactors | string[] |
tokenProviders | IList<IUserTwoFactorTokenProvider> |
Return value
This script must return a OtpMethodsByRequestedAuthenticationContextClass
object.
Example scripts
- Picking an OTP method based on the requested authentication context method class
OtpMethodsByRequestedAuthenticationContextClass otpMethod = new OtpMethodsByRequestedAuthenticationContextClass();
Uri requestedAuthnContextClass = requestedAuthenticationContextModel.RequestedAuthenticationContextClass.FirstOrDefault();
otpMethod.RequestedAuthenticationContextClass = requestedAuthnContextClass != null ? requestedAuthnContextClass.ToString() : string.Empty;
SystemLogger.Instance.WriteInformation("Requested authentication context class is: " + otpMethod.RequestedAuthenticationContextClass);
switch (otpMethod.RequestedAuthenticationContextClass)
{
case "urn:dk:gov:saml:attribute:AssuranceLevel:4":
otpMethod.Methods = new List<OtpType> { OtpType.WebAuthn };
break;
case "urn:dk:gov:saml:attribute:AssuranceLevel:3":
otpMethod.Methods = new List<OtpType> { OtpType.Authenticator };
break;
default:
otpMethod.Methods = new List<OtpType> { OtpType.Email, OtpType.Sms, OtpType.OS2faktor, OtpType.Authenticator, OtpType.Device, OtpType.WebAuthn };
break;
};
return otpMethod;
Validate authentication context class that is sent via an Authorization request from an OAuth/OIDC client application
Script usage
You can use a script of this type to validate authentication context class that is sent via an Authorization request from an OAuth/OIDC client application
Script input parameters
Name | Type |
---|---|
httpContext | HttpContextBase |
request | OpenIdConnectRequest |
oauthRequestInformation | OAuthRequestInformation |
configSection | OAuthMetadataConfigurationSection |
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
Example scripts
// Validate the authentication context
var acrValues = request.AcrValues;
SystemLogger.Instance.WriteInformation(string.Format("Authentication Context: {0}", acrValues));
if (acrValues != null && acrValues.Contains("urn:dk:gov:saml:attribute:AssuranceLevel:2"))
{
throw new ArgumentException("urn:dk:gov:saml:attribute:AssuranceLevel:2");
}
Customize step-up behavior of an OAuth/OIDC client application
Script usage
You can use a script of this type to customize the step-up behavior of an OAuth/OIDC client application
Script input parameters
Name | Type |
---|---|
httpContext | HttpContextBase |
openIdConnectRequest | OpenIdConnectRequest |
oauthRequestInformation | OAuthRequestInformation |
configSection | OAuthMetadataConfigurationSection |
reusableContext | PassiveProtocolContext |
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
supportsScope | bool |
Example scripts
string contextId = httpContext.Items[ProtocolConnectionContextItems.ContextIdKey] as string;
var temporaryProtocolContext = oauthRequestInformation.LoginContext;
if (reusableContext != null && oauthRequestInformation.IsSignOnRequest)
{
var authenticationConnectionId = reusableContext.Principal.GetAuthenticationConnectionId();
SystemLogger.Instance.WriteDebug("Set ReuseAuthenticationConnectionId = {0} and IsForcingAuthnDueToScopingOrStepUp = true", authenticationConnectionId);
ForceAuthenticationAndReuseIdentityProvider(reusableContext, temporaryProtocolContext);
return;
}
if (reusableContext == null && requestedAuthenticationContextModel.AuthnRequestHasRequestedAuthnContext)
{
SystemLogger.Instance.WriteDebug("AuthnRequestSignOnRequestArrivedObserver: reusableContext is null. Set IsForcingAuthnDueToScopingOrStepUp = true");
temporaryProtocolContext.IsForcingAuthnDueToStepUp = true;
}
#region additional methods
private static void ForceAuthenticationAndReuseIdentityProvider(PassiveProtocolContext reusableContext, SessionLoginContext temporaryProtocolContext)
{
SystemLogger.Instance.WriteDebug("ForceAuthenticationAndReuseIdentityProvider");
temporaryProtocolContext.ReuseAuthenticationConnectionId = reusableContext.Principal.GetAuthenticationConnectionId();
temporaryProtocolContext.IsForcingAuthnDueToStepUp = true;
}
#endregion additional methods
Select which Identity Providers an OAuth/OIDC client application can use (connection dependency customization)
Script usage
You can use a script of this type to specify which Identity Providers that an OAuth/OIDC client application can use. This script overrides the default behavior which uses the connection dependency list.
Script input parameters
Name | Type |
---|---|
connWithMethodClasses | AuthenticationConnectionWithMethodClass |
requestedAuthenticationContextModel | RequestedAuthenticationContextModel |
httpContext | HttpContextBase |
loginContext | SessionLoginContext |
Return value
This scripting requires to return a list of AuthenticationConnection
.
Example scripts
// Take 2 AuthenticationConnection from the list AuthenticationConnectionWithMethodClass
return connWithMethodClasses.Select(x => x.AuthenticationConnection).Take(2).ToList();
Validate authentication context class that is returned from an upstream OAuth/OIDC Identity Provider
Script usage
You can use a script of this type to validate authentication context class that is returned from an upstream OAuth/OIDC Identity Provider
Script input parameters
Name | Type |
---|---|
oauthTokenResponse | OAuthTokenResponse |
httpContext | HttpContextBase |
requestInformation | RequestInformation |
authConfig | OAuthProviderConnectionConfigurationSection |
Example scripts
SystemLogger.Instance.WriteDebug("Validate authentication context class that is returned from an upstream OAuth/OIDC Identity Provider");
var responseAccessToken = oauthTokenResponse.AccessToken;
SystemLogger.Instance.WriteDebug("oauthTokenResponse.AccessToken: {0}", oauthTokenResponse.AccessToken);
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var jsonTokenSecurity = jwtSecurityTokenHandler.ReadToken(responseAccessToken.TrimStart()) as JwtSecurityToken;
Customize authentication context class that is sent via an Authorization request to an upstream OAuth/OIDC Identity Provider
Script usage
You can use a script of this type to customize authentication context class that is sent via an Authorization request to an upstream OAuth/OIDC Identity Provider.
Script input parameters
Name | Type |
---|---|
httpContext | HttpContextBase |
requestInformation | RequestInformation |
authConfig | OAuthProviderConnectionConfigurationSection |
Return value
This scripting requires to return a string of authentication context class values.
Example scripts
// Return the authentication context class value
return "urn:dk:gov:saml:attribute:AssuranceLevel:2";
Customize an Authorization request that is sent to an upstream OAuth/OIDC Identity Provider
Script usage
You can use a script of this type to cutomize the content of an Authorization request that is sent to an upstream OAuth/OIDC Identity Provider.
Script input parameters
Name | Type |
---|---|
parameters | Dictionary<string, string> |
httpContext | HttpContextBase |
requestInformation | RequestInformation |
authConfig | OAuthProviderConnectionConfigurationSection |
Return value
This scripting requires to return a Dictionary<string, string>
of authorization request.
Example scripts
// Customize Authorization request dictionary
if (parameters.ContainsKey("acr_values"))
{
parameters["acr_values"] += " urn:dk:gov:saml:attribute:AssuranceLevel:1";
}
else
{
parameters["acr_values"] = "urn:dk:gov:saml:attribute:AssuranceLevel:1";
}
return parameters;
Generic provider
You can use a script of this type to create a credentical validator for a Generic Provider. Learn more about Scripting Generic provider here.
Identity Provider interceptor
You can use a script of this type to create an interceptor for a Identity Provider. Learn more about Identity Provider scripting interceptor here.
Service Provider interceptor
You can use a script of this type to create an interceptor for a Service Provider. Learn more about Service Provider scripting interceptor here.
Syntax
To reference a script defined in the Script library, you can use the following syntax:
UseScriptLibrary::{name-of-your-script-library}
For example:
UseScriptLibrary::CustomizeClaimsTransformation
Note that you cannot append more code after UseScriptLibrary::{}
because Identify treats everything after the "::" portion as a part of the script name
Additional assembly references
Normal scripting features have a fixed set of referenced assemblies. With Script library, you can add references to additional assemblies. Note that your custom assembly references must either exist in the [your tenant folder]\Admin\bin
and [your tenant folder]\Runtime\bin
folders or in GAC.
Warning: due to Identify's caching mechanism, making changes to the Additional assembly references setting only does not force a cache reload on Identify runtime. To force cache reload, you can add a space character at the end of the script and save.
Syntax:
name-of-the-file.dll
For example:
Safewhere.External.dll
Additional namespaces
Warning: due to Identify's caching mechanism, making changes to the Additional assembly references setting only does not force a cache reload on Identify runtime. To force cache reload, you can add a space character at the end of the script and save.
Like additional references, you can also specify additional namespaces for your scripts.
Syntax:
using {your-additional-namespace};
For example:
using System.Collections;
Some very common .NET namespaces and Identify's namespaces have been added by default already. You can find that list below:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Xml;
using System.IO;
using System.IdentityModel;
using System.IdentityModel.Tokens;
using Safewhere.ModelFoundation.Kernel;
using Safewhere.ModelFoundation.Kernel.CommonLogging;
using Safewhere.ServiceProvider.Xml.RequestTypes;
using Safewhere.IdentityProviderModel;
using Safewhere.IdentityProviderModel.ContextualClaimTransformation;
using Safewhere.IdentityProviderModel.Claims;
using Safewhere.IdentityProviderModel.PolicyScript;
using Safewhere.IdentityProviderModel.SessionState;
using Safewhere.IdentityProvider.RuntimeModel;
using Safewhere.IdentityProvider.Saml2.Authentication;
using Safewhere.IdentityProvider.Saml2.Protocol;
using Safewhere.IdentityProvider.Saml2;
using Safewhere.IdentityModel.Core;
using Safewhere.ServiceProvider.ServiceProviderConstants;
using Safewhere.IdentityProvider.Otp;
using Safewhere.IdentityProviderModel.DomainEvent;
Note that, for the "full class script" such as Generic provider or interceptor scripts, you can use extra using statements at the begin of the script:
Delete a script library that is in use
You cannot delete a script library that is currently in use:
Debug information
A script can trigger an error when applied during runtime. To pinpoint the specific line of code responsible for the error, you can enable debug information by adding a key to the web.config file in the Runtime.
<add key="KeepCompileFiles" value="true"/>
For example:
In this example, the temporary script file 'yqjmhqk3.0.cs' is retained, and "Debug information" is enabled to precisely identify the line of code responsible for the error.
Please be aware that this key is intended solely for debugging purposes, and it should be removed promptly after completing your debugging tasks.