In addition to supporting all the functions defined in the OASIS policy model, Enforcer also provides an extension point for you to provide your own functions, which can be invoked by the Policy Decision Point during policy evaluation.
Nb. Custom functions should be quick to evaluate and return computed values for the policies to operate on. Functions should not touch external dependencies, perform any asynchronous work, or have side effects.
- If data from an external system is required, implement a Policy Information Point.
- If a policy evaluation should have side effects, implement an Outcome Action Handler.
Function Definitions in ALFA
To call a function from ALFA, the functions must first be defined in ALFA. Functions are defined using the function
keyword, an ALFA name, a unique function id, and the function signature.
The following example is a function definition to add two integers and return an integer result:
namespace CalculatorFunctions
{
function Add = "com.acmecorp:calculator-add" : integer integer -> integer
}
The ALFA identifier for this function is CalculatorFunctions.Add
. The unique function id is com.acmecorp:calculator-add
. The function id binds the ALFA function to an implementation in .Net.
Unlike the OASIS model, which requires a unique id per function signature, Enforcer allows for function overloading. Functions can share the same name and have different argument lists. Function overloads are specified using the pipe operator.
namespace CalculatorFunctions
{
function Add = "com.acmecorp:calculator-add" : integer integer -> integer
| double double -> double
}
Function implementations in .Net
Functions must have an implementation in .Net. Enforcer will invoke the appropriate function with a matching signature when called from an ALFA expression or condition.
For Enforcer to associate a function implementation with the ALFA declaration, the method in .Net must be:
- Declared in a public class
- Declared as public and static
- Decorated with the
PolicyFunction
attribute - Discovered by Enforcer during start-up
The PolicyFunction
attribute has a single argument which specifies the ALFA function id. The id must match the id used in the ALFA declaration. When functions are overloaded, use the same function id for each overload. The names of the classes and methods are not significant.
By default, functions decorated with the PolicyFunction
attribute are only discovered in the executing assembly. If you want to use functions defined in another assembly then you need to call AddFunctionAssembly
on the Enforcer options object at start-up.
Implementations for the function declaration in the previous section as follows:
namespace Acmecorp
{
public class CalculatorFunctions
{
[PolicyFunction("com.acmecorp:calculator-add")]
public static int Add(long first, long second)
{
return first + second;
}
[PolicyFunction("com.acmecorp:calculator-add")]
public static double Add(double first, double second)
{
return first + second;
}
}
}
Argument data types
The following table shows the relationship between ALFA data types, and the argument types required in a custom function. Note that integer types are treated as 64-bit.
ALFA type | .Net type |
---|---|
integer | long |
double | double |
boolean | bool |
date | Rsk.Enforcer.PolicyModels.Date |
time | Rsk.Enforcer.PolicyModels.Time |
dateTime | System.DateTime |
duration | System.TimeSpan |
Bag arguments
Bag types, as defined in the OASIS policy model, can be passed to functions. In this instance, your function implementation should accept an IReadOnlyCollection<T>
, where T
is the .Net equivalent of the bagged type.
The following is an ALFA definition and .Net implementation of a function that accepts an attribute bag as a parameter.
ALFA
namespace CollectionFunctions
{
function First = "com.acmecorp:collection-first" : bag[integer] -> integer
}
.Net
namespace Acmecorp
{
public class CollectionFunctions
{
[PolicyFunction("com.acmecorp:collection-first")]
public static long First(System.Collections.Generic.IReadOnlyCollection<long> bag)
{
return bag.First();
}
}
}
Variable numbers of arguments
The OASIS specification allows for functions with variable numbers of arguments. In ALFA, this is denoted by using an asterisk on the final argument. In the .Net code, this maps to a params T[]
array.
Example: ALFA
namespace CollectionFunctions
{
function Sum = "com.acmecorp:collection-sum" : integer* -> integer
}
.Net
namespace Acmecorp
{
public class CollectionFunctions
{
[PolicyFunction("com.acmecorp:collection-sum")]
public static long Sum(params long[] args)
{
long sum = 0;
foreach (long arg in args)
{
sum += arg;
}
return sum;
}
}
}
Named arguments
Enforcer supports named arguments in function definitions in order to provide additional clarity when developing in ALFA. Named arguments are not part of of the OASIS specification and are entirely optional. When functions are bound to a .Net implementation, the names of the arguments are ignored and binding relies entirely on the function signature. ALFA language keywords cannot be used as argument names.
When naming arguments in a function definition, the name should preceed the type, separated by a colon. The following example shows a function definition with named arguments:
namespace Oasis.Functions
{
function IsMatch = "urn:oasis:names:tc:xacml:1.0:function:string-regexp-match"
: pattern:string toTest:string -> boolean
}
Function Failures
In the event that the desired behaviour is that a function call should fail, the .Net function implementation should throw a Rsk.Enforcer.Oasis.Functions.PolicyProcessingErrorException
. In this case, Enforcer will mark the policy result as Indeterminate and write details of the failure to the diagnostic logs.