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.
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 menu
A script has the following properties:
- Name: name of a script
- Script type: please see section Script type
- Script: a script is written in CSharp language and is compiled against .NET framework 4.8
- Support functions by putting them in a predefined #region (
#region additional methods
---#endregion additional methods
)
- Support functions by putting them in a predefined #region (
- 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 14 supported script types:
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 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 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 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
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 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 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;
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;
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.