Multi-line rule executor
A need often exists to build multi-line rules to compute a wanted value for an attribute. IBM® Security Verify offers the ability to configure both simple single-line and more advanced multi-line rules.
For single-line syntax rules, see Attribute functions.
Elements of a multi-line rule
The multi-line rule is written in YAML format. It is constrained by the formatting needs of a YAML document. The YAML parser that is used supports most of YAML 1.1 and 1.2.
- Statement
- A statement is the building block of the rule and can be of different types. For example, the return statement computes the expression that was provided and returns the expression as the rule value.
- Block
- A block is a bounded collection of statements. The rule starts with a top-level block called
statements
. Blocks can exist within certain statement types. For example, theif.block
is the collection of statements to be executed when the correspondingif.match
statement evaluates to true. - Block context
- A block context is a collection of variables that are available within that block. Subblocks can access the context of the parent blocks. Parent blocks cannot access the context of the subblocks.
- Expression
- An expression is a snippet that uses the same syntax as single-lined expressions. See Attribute functions.
Supported statement types
- Context
- Return
- If
Context statement
context
statement is used to initialize and assign values to named variables.
It supports two operators.:=
- This operator is used to initialize a new variable within the context block where the statement
is evaluated. For example, if a variable is initialized within an
if.block
, it is not available outside this block in the subsequent statements.
=
- This operator is used to assign a value to an existing variable. The variable can exist in the current context block or a parent block.
context: {{varname}} := {{expression}}
. Use is
context.{{varname}}
to access {{varname}}
in subsequent statements
in the same block or subblocks.Return statement
The return statement is used to indicate to the rule engine to end the execution and to immediately return the value that was computed.
The format is return: {{expression}}
.
If statement
The if
statement is used to build conditional blocks. It consists of several
properties, unlike the previous two statements.
Name | Description | Format |
---|---|---|
match |
If this condition evaluates to true, the corresponding block is
executed. |
match: 1 == 2 |
block |
Bounded block of statements that must be executed if match evaluates to
true. |
A standard YAML array of statements |
elsifs |
Array of else-if conditions that are evaluated if match fails to evaluate to
true. |
YAML arrays that are nested if statements |
else |
Block of statements that are executed if match and all
elseifs.match fail to evaluate to true. |
A YAML array of statements |
Trace logging
Trace logging can be added to a multi-line rule to send debug logs. The trace logging properties
are executed if a rule is executed with enabled trace mode. Trace logging is supported through the
debug
and debugx
properties.
debug
statement-
The
debug
statement is used to indicate to execute the expression that was provided and log the value that was returned. The expression should always evaluate to a string.Format -
debug: {{expression}}
debugx
block-
The
debugx
block consists of two properties:log
andfields
.Name Description Format log The expression provided is evaluated and the value returned is logged. The expression should always evaluate to a string. log: {{expression}}
fields Custom metadata fields that are sent with the log. Provided as map of key-value pairs. The value of each pair is an expression, which must evaluate to a string. {{field_key}}: {{expression}}
Refer to the section for a sample function that illustrates trace logging.
Snippets
This list of snippets is not exhaustive and does not cover all possible uses of rules. However, this list provides some samples that can be used as is or can be extended.
Get the manager's display name
statements:
- context: manager := user.getManager()
- context: userExists := has(context.manager.name)
- context: >
givenNameExists := context.userExists
&& has(context.manager.name.givenName)
&& context.manager.name.givenName != ""
- context: familyNameExists := context.userExists && has(context.manager.name.familyName) && context.manager.name.familyName != ""
- context: formattedExists := context.userExists && has(context.manager.name.formatted) && context.manager.name.formatted != ""
- if:
match: context.formattedExists
block:
- return: context.manager.name.formatted
elseifs:
- match: context.givenNameExists
block:
- if:
match: context.familyNameExists
block:
- context: managerName := context.manager.name.familyName + ", " + context.manager.name.givenName
- return: context.managerName
else:
- return: context.manager.name.givenName
- match: context.familyNameExists
block:
- return: context.manager.name.familyName
- return: string("Not Available")
Transform the email domain
statements:
- context: "workEmails := has(user.emails) ? user.emails.filter(e, e.type == 'work') : []"
- context: "workEmail := size(context.workEmails) > 0 ? context.workEmails[0].value : ''"
- if:
match: context.workEmail != ""
block:
- context: cn := context.workEmail.substring(0, context.workEmail.lastIndexOf('@'))
- if:
match: context.cn != ""
block:
- return: context.cn + "@github.com"
- return: ""
Scoping of variables
statements:
- context: ret := 3 + 4
- if:
match: 2 > 1block:
- context: ret := 5
- if:
match: 3 > 2block:
- context: ret = 0
- return: context.ret
The result is 7. This result is because line 6 reinitializes ret
within the
if.block
. The subsequent nested statements see this variable. However, after the
rule engine exits the if.block
, it can access the variable that was initialized at
line 2.
Trace logging example
This example illustrates how to use trace logging statements.
statements:
- context: uid := user.id
- context: userEmail := "user@test.com"
- debug: '"This is an example of a trace log at time " + string(now)'
- debugx:
log: '"The user id obtained: " + context.uid'
fields:
flow: '"login"'
time: 'string(now)'
userEmail: context.userEmail
- return: context.uid + ", " + context.userEmail
For the debug
statement, the expression provided will be evaluated and logged as
"This is an example of a trace log at time <timestamp>"
.
debugx
block, the expression provided will be evaluated and logged as
"The user id obtained: <user id>"
. The following custom metadata fields will be
sent along with the log.
Key | Value |
---|---|
"flow" |
"login" |
"time" |
"<timestamp>" |
"userEmail" |
"user@test.com" |
Important restrictions
- The variables that are initialized by the
context
frame can live only within that frame or subframes within it. In the previous rule example, the variablemanagerName
is not accessible outside theif
frame that it was initialized in. elseifs
andelse
frames can be used only if anif
frame is on the same level.- All the
if
andelseifs
frame must have amatch
expression that always returns a Boolean value. - If the rule evaluation flow does not encounter a
return
statement, a null value is returned. No errors are thrown nor are syntax check performed to make sure that all possible flows have areturn
. The author of the rule must ensure that all flows of the rule return an acceptable value.