Expressions

This article will describe how to use expressions with PhenixID Server. Expressions are set in the configuration parameters exec_if_expr or skip_if_expr. These configuration parameters are used to configure logic defining if a valve should be executed or not based on runtime data, such as request parameter values.

All PhenixID runtime objects can be addressed in an expression. For more information about the PhenixID Data Model, please read this article.

For valves executing on the item set, item_include_expr may also be added. This parameter is used to define which items in the item set that should be included in the execution.

Expressions are written with javascript. An expression must return true or false.

The settings should be made by logging in to the configuration GUI, go to Scenarios, Radius/Federation,<your scenario>, press the tab "Execution flow" and edit the Pipe/Valve in question. Set the parameters and values according to examples below.

When making changes to the configuration, please make sure to have backup of the file/files.

Request

Methods

String getOrDefault(String parameterName, String defaultValue)

Returns the value of the request parameter parameterName.

Returns defaultValue if parameterName is not part of the request.

 

 

Usage

The request methods can be addressed using the syntax request.method()

Examples

Request parameter contains a value

"exec_if_expr": "request.getOrDefault('User-Name','').contains('abc123')"

Request parameter contains a value, will also match if parameter is missing

"exec_if_expr": "request.getOrDefault('User-Name','abc123').contains('abc123')"

Request parameter exists

"exec_if_expr": "!request.getOrDefault('customParam','not_exist).equals('not_exist')"

Request parameter with a specific length (6 or 8 chars)

"skip_if_expr": "request.getOrDefault('User-Password','').length==6||request.getOrDefault('User-Password','').length==8" 

Items

About

Items are different from the other objects. There can be 0-* items in the flow (runtime execution). Hence, one must address which item object to use. Normally, in authentication scenarios, there is only one item in the flow.

To get a handle to an Item object for exec_if_expr and skip_if_expr, the Flow object must be used.

For item_include_expr expressions, the current item is already targeted.

 

Methods - Flow

Item firstItem()

Returns the first item in the item set

boolean isEmpty()

Returns true if there are no items in the flow, otherwise false

boolean isSingle()

Returns true if there is one item in the flow, otherwise false

boolean isMulti()

Returns true if there is more than one item in the flow, otherwise false

Item item(int x)

Returns the x:th item in the item set

String getPropertyValue(String propertyName)

Returns the value of the property propertyName on the first item in the flow. If  propertyName is not present on the first item, an empty string is returned.

String property(String propertyName)

Same functionality as getPropertyValue

String property(int x, String propertyName)

Returns the value of the property propertyName on the x:th item in the flow. If  propertyName is not present on the x:th item, an empty string is returned.

String getPropertyValue(String propertyName, String defaultValue)

Returns the value of the property propertyName on the first item in the flow. If  propertyName is not present on the first item, defaultValue is returned.

 

 

Usage

The flow methods can be addressed using the syntax flow.method()

Methods - Item

String getId()

Returns the id of the item.

boolean containsProperty(String propertyName)

Returns true if the property propertyName is present on the item, otherwise false.

String getPropertyValue(String propertyName)

Returns the value of the property propertyName. If  propertyName is not present, null is returned

String getPropertyValue(String propertyName, String defaultValue)

Returns the value of the property propertyName. If  propertyName is not present, defaultValue is returned.

 

 

Usage

The Item methods can be addressed using the syntax item.method()

Examples - Flow

If no items exist

"exec_if_expr": "flow.isEmpty()"

If many items exist

"exec_if_expr": "flow.isMulti()"

If one item exists

"exec_if_expr": "flow.isSingle()"

When a property on the first item equals a value. Any example below can be used to achieve this.

"exec_if_expr" : "flow.property('SMSAuth').equals('true')"
"exec_if_expr" : "flow.getPropertyValue('SMSAuth').equals('true')"

When a property on the first item equals a value. Will also match if the property is missing on the first item.

"exec_if_expr" : "flow.getPropertyValue('SMSAuth','true').equals('true')"

When a property on the first item equals an empty string

"exec_if_expr" : "flow.property('AttributeID').isEmpty()"

When a property on the second item equals an empty string

"exec_if_expr" : "var myItem=flow.item(1); myItem.property('AttributeID').isEmpty()"

Test a session property against an item property.

<p>"skip_if_expr": "parseInt(session.get('authLevel')) &gt; parseInt(flow.getPropertyValue('requiredLOA'))"</p>
Click to copy

If property exist

"exec_if_expr" : "flow.firstItem().containsProperty('mobile')"
Click to copy

If item(s) exist and value of property matches.

Will not give an error if property doesn't exist.

"exec_if_expr": "!flow.isEmpty() && request.getOrDefault('distinguishedName','A').contains('dc=domain.local')"
Click to copy

Examples - Item

Is property present

"exec_if_expr": "var myItem=flow.firstItem(); myItem.containsProperty('organizationId')"

Include items when property equals a value

"item_include_expr" : "item.getPropertyValue('Approved','')===('0')"

Include items when property equals a value. Will also match if the property is missing.

"item_include_expr" : "item.getPropertyValue('Approved','0')===('0')"

Include items when a property exists.

<p>"item_include_expr": "item.containsProperty('userPersonalNumber')"</p>
Click to copy

Include items when item id contains a value

"item_include_expr" : "item.getId().contains('ou=sales')"

Session

Methods

String id()

Returns the session id.

String userId()

Returns the userID value the session is bound to.

boolean isAuthenticated()

Returns true if the session is authenticated, otherwise false.

String get(String parameterName)

Returns the value of the session parameter parameterName.

boolean isUserInRole(String roleName)

Returns true if the user the session is bound to has the role roleName, otherwise false.

String language()

Returns the session language.

 

 

Usage

The session methods can be addressed using the syntax session.method()

NB! The session must be loaded by previous valve (SessionLoadValve) in pipe.

Examples - Session

Is the session authenticated?

"skip_if_expr": "session.isAuthenticated()" 

Session property contains a value

"exec_if_expr": "session.get('authenticationMethod').equals('otp')" 

Is user member of role?

"exec_if_expr": "session.isUserInRole('sysadmin')" 

Session property title exists and has value E.
This example is from a Dispatcher Authenticator

"expression": "session.get('title') !== null && session.get('title').equals('E')"

Session property exists?
Support multi values.

"exec_if_expr": "session.properties().contains('carLicense')"
Click to copy

Attributes

Properties

String <propertyName>

Returns the value of the attributes propertyName

 

 

Usage

The attributes properties can be addressed using the syntax attributes.propertyName

Examples

Attribute property equals a value

"exec_if_expr": "attributes.myAttribProperty.equals('hwtoken')" 

Attribute property equals true

"exec_if_expr": "attributes.myAttribProperty.equals(true)" 

Act on user_authenticated or token_authenticated (example below will skip, if login has been successful)

"skip_if_expr" : "(attributes.token_authenticated!=null && attributes.token_authenticated.equals(true)) || (attributes.user_authenticated!=null && attributes.user_authenticated.equals(true))"
Click to copy

Other examples

OR expression

"exec_if_expr" : "flow.property('APPAuth').equals('true') || flow.property('DeviceAuth').equals('true')"

 

AND expression

"exec_if_expr" : "flow.property('TokenAuth').equals('true') && flow.firstItem.getId().equals('uid=aeldebrink,ou=Users,dc=example,dc=org')"

 

Example configuration, fallback to sms if user has not enrolled for Pocket Pass

This is an example configuration, where we will deliver an SMS if the user has not enrolled for Pocket Pass. The Scenario used is Username, Password & OTP delivered by SMS. Changes to the configuration is in red.

So please go to Scenarios, Radius, <your scenario> and the tab "Execution flow". We will start in the Pipe that does LDAPSearch and bind.

Press "+ Add valve", "Type" should be "GetTokenExistsValve", set it to "Enabled" and add the parameters:

username_attribute with value User-Name
get_value_attribute_key with value HasToken

Then press "Add valve".

Should look like this (press JSON in rigt corner):

{
	"username_attribute": "User-Name",
	"get_value_attribute_key": "HasToken"
}

Drag the valve so it's located before OTPGeneratorValve and then press "Save".

We also need to add expressions to the valves OTPGeneratorValve and OTPBySMSValve. So on these valves press "+ Add" and enter the parameter and value. In the example below we have used both  "exec_if_expr" and "skip_if_expr" with the values  "flow.property('HasToken').equals('false')" and "flow.property('HasToken').equals('true')".

So with these added configuration we will use GetTokenExistsValve to see if the user has enrolled for a token or not. Then we will generate and deliver an SMS depending on the result from GetTokenExistsValve.

Configuration should now look like this:

{
    "name": "LDAPSearchValve",
    "config": {
        "connection_ref": "f56b30ab-5042-4ca0-b9f0-bc7e36a12fde",
	"base_dn": "DC=Org,DC=local",
	"scope": "SUB",
	"size_limit": "0",
	"filter_template": "samAccountName={{request.User-Name}}",
	"attributes": "mobile"
        }
},{
    "name": "LDAPBindValve",
    "config": {
	"connection_ref": "f56b30ab-5042-4ca0-b9f0-bc7e36a12fde",
	"password_param_name": "User-Password"
        }
},{
    "name": "GetTokenExistsValve",
    "config": {
	"username_attribute" : "User-Name",
	"get_value_attribute_key" : "HasToken"
        }
},{
    "name": "OTPGeneratorValve",
    "config": {
	"name": "generated_otp",
	"length": "4",
	"alpha_numeric": "false",
	"valid_time_in_seconds": "120",
	"exec_if_expr": "flow.property('HasToken').equals('false')"
        }
},{
    "name": "OTPBySMSValve",
    "config": {
	"message_gateway_settings": "dd5f8934-b955-4829-9bc5-20f4a0e74a58",
	"recipient_param_name": "mobile",
	"generated_otp_name": "generated_otp",
	"use_flash": "true",
	"skip_if_expr": "flow.property('HasToken').equals('true')"
        }
}

Now we need to do the same on our pipe for the validation of otp. No LDAPSearch and bind is needed here. But we need to add the the GetTokenExistsValve to know what valve to use for the validation. We also need to add a ItemCreateValve and a valve for the validation using tokens, that valve is TokenValidationValve. After this we add the expressions to use the correct validation depending OTP method.

So please add the new valves like we did above. Parameters and values for GetTokenExistsValve is the same as before:

username_attribute with value User-Name
get_value_attribute_key with value HasToken

The TokenValidationValve should have the following set:

provided_otp_param_name with value {{request.User-Password}}
debug_token_data with value true
otp_length with value 6
exec_if_expr with value flow.property('HasToken').equals('true')

These two new valves should be placed right after the SessionLoadValve, according to the configuration below.

Last step is to add expression to the OTPValidationValve.

Configuration should now look like this:

{
    "name":"SessionLoadValve",
    "config": {
	"id": "{{request.State}}"
        }
},{
    "name": "ItemCreateValve",
    "config": {
        "dest_id" : "data4"
        }
},{
    "name": "GetTokenExistsValve",
    "config": {
        "username_attribute" : "User-Name",
        "get_value_attribute_key" : "HasToken"
        }
},{
    "name": "TokenValidationValve",
    "config": {
        "provided_otp_param_name" : "{{request.User-Password}}",
        "debug_token_data" : "true",
        "otp_length" : "6",
        "exec_if_expr" : "flow.property('HasToken').equals('true')"
        }
},{
    "name": "OTPValidationValve",
    "config": {
        "provided_otp_param_name" : "{{request.User-Password}}",
        "generated_otp_param_name" : "generated_otp",
        "exec_if_expr" : "flow.property('HasToken').equals('false')"
        } 
} 

Example configuration, group membership

This is an example configuration, where we will decide OTP method depending on group membership. The Scenario used is Username, Password & OTP delivered by SMS. Changes to the configuration is in red.

Please go to Scenarios, Radius, <your scenario> and the tab "Execution flow". We will start in the Pipe that does LDAPSearch and bind, where we now need two different LDAPSearch, one for each group. On the first search we will retrieve the value for mobile if user is a member of the group SMS, if not we proceed to the next search since "proceed on error" is set to true. If we have the property mobile we will generate and send OTP.

Add the second LDAPSearchValve as well as parameters and values according to the following configuration:

{
    "name": "LDAPSearchValve",
    "config": {
        "connection_ref" : "7571a019-77c4-4664-87a7-80b7fb1341eb",
        "base_dn" : "DC=Org,DC=local",
        "scope" : "SUB",
        "size_limit" : "0",
        "filter_template" : "(&(samAccountName={{request.User-Name}})(memberOf=cn=SMS,ou=Users,ou=PhenixID,dc=org,dc=local))",
        "attributes" : "mobile",
        "proceed_on_error" : "true"
        }
},{
    "name": "LDAPSearchValve",
    "config": {
        "connection_ref" : "7571a019-77c4-4664-87a7-80b7fb1341eb",
        "base_dn" : "DC=Org,DC=local",
        "scope" : "SUB",
        "size_limit" : "0",
        "filter_template" : "(&(samAccountName={{request.User-Name}})(memberOf=cn=Token,ou=Users,ou=PhenixID,dc=org,dc=local))",
        "attributes" : ""
        }
},{
    "name": "LDAPBindValve",
    "config": {
        "connection_ref" : "7571a019-77c4-4664-87a7-80b7fb1341eb",
        "password_param_name" : "User-Password"
        }
},{
    "name": "OTPGeneratorValve",
    "config": {
        "length" : "4",
        "alpha_numeric" : "false",
        "name" : "generated_otp",
        "valid_time_in_seconds" : "120",
        "exec_if_expr" : "flow.firstItem().containsProperty('mobile')"
        }
},{
    "name": "OTPBySMSValve",
    "config": {
        "gw_username" : "gwuseraccount",
        "gw_password" : "{enc}GRmxWFJgpWsJdEFd7vOPnwFnLAnbMLwIeqFf5d8Z5Jc=",
        "recipient_param_name" : "mobile",
        "generated_otp_name" : "generated_otp",
        "use_flash" : "true",
        "exec_if_expr" : "flow.firstItem().containsProperty('mobile')"
        }
}

Now we need to do the same on our pipe for the validation of otp. We need the same two LDAP searches here. As well as the valve for the validation using tokens, TokenValidationValve. After this we add the expressions to use the correct validation depending OTP method.

So please add the two new valves as well as parameters and values according to the following configuration:

{
    "name": "LDAPSearchValve",
    "config": {
        "connection_ref" : "7571a019-77c4-4664-87a7-80b7fb1341eb",
        "base_dn" : "DC=Org,DC=local",
        "scope" : "SUB",
        "size_limit" : "0",
        "filter_template" : "(&(samAccountName={{request.User-Name}})(memberOf=cn=SMS,ou=Users,ou=PhenixID,dc=org,dc=local))",
        "attributes" : "mobile",
        "proceed_on_error" : "true"
        }
},{
    "name": "LDAPSearchValve",
    "config": {
        "connection_ref" : "7571a019-77c4-4664-87a7-80b7fb1341eb",
        "base_dn" : "DC=Org,DC=local",
        "scope" : "SUB",
        "size_limit" : "0",
        "filter_template" : "(&(samAccountName={{request.User-Name}})(memberOf=cn=Token,ou=Users,ou=PhenixID,dc=org,dc=local))",
        "attributes" : ""
        }
},{
    "name": "SessionLoadValve",
    "config": {
        "id" : "{{request.State}}"
        }
},{
    "name": "TokenValidationValve",
    "config": {
        "provided_otp_param_name" : "{{request.User-Password}}",
        "debug_token_data" : "true",
        "otp_length" : "6",
        "skip_if_expr" : "flow.firstItem().containsProperty('mobile')"
        }
},{
    "name": "OTPValidationValve",
    "config" : {
        "provided_otp_param_name" : "{{request.User-Password}}",
        "generated_otp_param_name" : "generated_otp",
        "exec_if_expr" : "flow.firstItem().containsProperty('mobile')"
        }
}