# Policy Condition

Conditions are probably the most powerful aspect of the JSON policy framework as it gives the ability to make any statement or param conditional. They are evaluated at runtime and can take into consideration dynamic variables from the application context (environment variable, user identity, HTTP request, etc).

The purpose of the conditions is to determine if any given statement or param is classified as applicable. This is done by evaluating all the types of conditions and returning a single boolean true if applicable.

{
    "Condition": {
        "Operator": "AND|OR",
        "<condition_type>": {
            "Operator": "AND|OR",
            "<left_operand>": "<right_operand>",
            ...
        },
        ...
    }
}

By default, the framework supports a few types of conditions, however, it can easily be extended with custom types. For more information please refer to the respective language library reference page.

To eliminate any misunderstandings, in this section I'm using the following terms to describe certain aspects of the policy conditions:

  • "Type" is the condition type like Equals, In or Regex
  • "Operand" is either the left or the right part of the condition pair within a group. For example, in the sample condition below, the left operand is ${USER.email} and the right operand is *@gmail.com
  • "Group" is the set of conditions of the same type. For example below is the "Like" group.
{
    ...
    "Condition": {
        "Like": {
            "${USER.email}": "*@gmail.com",
            "${USER.fullname}": "John*"
        }
    }
}

The Condition attribute may contain more than one type of condition.

# Operator

  • Type: String
  • Default Value: AND
  • Supported Values: AND, OR
  • Optional

The Operator attribute defines the logical operator between condition types or within a specific group. If none specified, the default operator is AND.

{
    "Condition": {
        "Operator": "OR",
        "Equals": {
            "${DATATIME.D}": "Mon"
        },
        "In": {
            "${USER.email}": [
                "admin@mydomain.com",
                "editor@mydomain.com"
            ]
        }
    }
}

The above condition is evaluated to true if today is Monday or the user's email address is either admin@mydomain.com or editor@mydomain.com.

As you might notice, a group may contain more than one condition and by default, the logical AND operator is used within the group. This way you can build conditions like "If a user's first name starts with 'Ja' and the last name starts with 'Mor'". However, you can customize the logical operator with the Operator attribute.

{
    "Condition": {
        "Operator": "OR",
        "Like": {
            "Operator": "OR",
            "${USER.first_name}": "Ja*",
            "${USER.last_name}": "Mor*"
        },
        "In": {
            "${USER.status}": [
                "pastdue",
                "pending"
            ]
        }
    }
}

The above conditions check if the user's first name starts with Ja or the last name starts with Mor, or the user's status is either pastdue or pending.

# Left Operand Naming Collision

Because policies are defined as JSON documents, you cannot have two exactly named left operands within the same scope. For example, if you want to cover the condition like "If user's city equals London or Dubai", the following condition is not going to work:

{
    "Condition": {
        "Equals": {
            "${USER.city}": "London",
            "${USER.city}": "Dubai"
        }
    }
}

Depending on the programming language, the above condition will be parsed to check only for one city (either London or Dubai).

To mitigate the left operand naming collision, you can define the right operand as an array of values.

{
    "Condition": {
        "Equals": {
            "${USER.city}": [
                "London",
                "Dubai"
            ]
        }
    }
}

# Equals

  • Type: ConditionObject

Check if the left and the right operands are identical (contain the same value and type of value). When dynamic markers are used, you should consider applying typecasting.

{
    "Condition": {
        "Equals": {
            "${HTTP_GET.ref}": "qa2020",
            "(*bool)${SESSION.is_authenticated}": true
        }
    }
}

The condition above checks if HTTP query param ref equals to qa2020 and user session's property is_authenticated equals to boolean true value.

The right operand may contain a single scalar value or array of scalar values. In the second case, the condition will be evaluated to true if the left operand matches at least one value in the array.

{
    "Condition": {
        "Equals": {
            "${USER.city}": [
                "London",
                "Dubai"
            ]
        }
    }
}

The "Equals" group type may also contain more than one condition. Each condition is evaluated independently and merged together with the logical operator that is defined within the group (the default logical operator is AND).

{
    "Condition": {
        "Equals": {
            "Operator": "OR",
            "${USER.city}": [
                "London",
                "Dubai"
            ],
            "${USER.name}": "Bruce Lee"
        }
    }
}

The condition above returns true if a user's city is London or Dubai, or the user's name is Bruce Lee.

# NotEquals

  • Type: ConditionObject

Check if the left and the right operands are not identical (either contain different values or type of values mismatch). When dynamic markers are used, you should consider applying typecasting.

{
    "Condition": {
        "NotEquals": {
            "${USER.name}": "John Dawn"
        }
    }
}

The right operand may contain a single scalar value or array of scalar values. In the second case, the condition will be evaluated to true if the left operand does not have the exact match to any value in the array.

{
    "Condition": {
        "NotEquals": {
            "${BOAT.color}": [
                "red",
                "blue"
            ]
        }
    }
}

In the same way, as with the Equals type, the NotEquals may contain more than one condition and they are evaluated and merged with the logical operator the same way.

# Between

  • Type: Object

Check if the left operand is within one or more ranges defined in the right operand. This type of condition does not take into consideration value type and depending on the programming language, the value dynamically casts before comparison.

The "range" is described by two values [min, max] and the condition evaluates if the given value is greater or equals to min and lower or equals to max.

{
    "Condition": {
        "Between": {
            "${USER.age}": [
                13,
                20
            ]
        }
    }
}

In case there is a need to compare against multiple ranges, you can define an array of ranges in the right operand as following:

{
    "Condition": {
        "Between": {
            "${DATETIME.H}": [
                [0, 6]
                [18, 23]
            ]
        }
    }
}

The above statement checks if the current hour is between 12am and 6am or 6pm and 11pm.

The Between group type may contain more than one condition. They are evaluated independently and merged with the logical operator defined in the Operator property.

# Greater

  • Type: ConditionObject

Check if the left operand is greater than the right operand. The comparing values type cast automatically by the programming language of your choice.

{
    "Condition": {
        "Greater": {
            "2020": "${TASK.year_created}"
        }
    }
}

The right operand may contain a single scalar value or array of scalar values. In the second case, the condition evaluates to true if the left operand is greater than at least one value in the array.

{
    "Condition": {
        "Greater": {
            "2020-10-01": [
                "2020-11-01",
                "2020-09-17"
            ]
        }
    }
}

The above condition is evaluated to true because 2020-10-01 is greater than 2020-09-17.

# Less

  • Type: ConditionObject

Check if the left operand is less than the right operand. The comparing values type cast automatically by the programming language of your choice.

{
    "Condition": {
        "Less": {
            "${ASSIGNMENT.rate}": 300.25
        }
    }
}

The right operand may contain a single scalar value or array of scalar values. In the second case, the condition evaluates to true if the left operand is less than at least one value in the array.

{
    "Condition": {
        "Less": {
            "2020": [
                "2021",
                "2019"
            ]
        }
    }
}

The above condition is evaluated to true because 2020 is less than 2021.

# GreaterOrEquals

  • Type: ConditionObject

Check if the left operand is greater or equals than the right operand. The comparing values type cast automatically by the programming language of your choice.

{
    "Condition": {
        "GreaterOrEquals": {
            "${Cargo.weight}": 6500
        }
    }
}

The right operand may contain a single scalar value or array of scalar values. In the second case, the condition evaluates to true if the left operand is greater or equals to at least one value in the array.

{
    "Condition": {
        "GreaterOrEquals": {
            "F": [
                "A",
                "Z"
            ]
        }
    }
}

The above condition is evaluated to true because F char is greater than A.

# LessOrEquals

  • Type: ConditionObject

Check if the left operand is less or equals to the right operand. The comparing values type cast automatically by the programming language of your choice.

{
    "Condition": {
        "LessOrEquals": {
            "${USER.age}": 20
        }
    }
}

The right operand may contain a single scalar value or array of scalar values. In the second case, the condition evaluates to true if the left operand is less or equals to at least one value in the array.

{
    "Condition": {
        "LessOrEquals": {
            "${PACKAGE.weight}": [
                10,
                30.5
            ]
        }
    }
}

# In

  • Type: ConditionObject

Check if the left operand is included in the array of values from the right operand. This type of condition checks for the identical match (both value and value type has to be identical).

{
    "Condition": {
        "In": {
            "${BOT.name}": [
                "google",
                "yahoo",
                "${ENV.allowed_bot}"
            ]
        }
    }
}

# NotIn

  • Type: ConditionObject

Check if the left operand is not included in the array of values from the right operand. This type of condition checks for the identical match (both value and value type has to be identical).

{
    "Condition": {
        "NotIn": {
            "${UserEntity.username}": "(*array)${DB.suspended_users}"
        }
    }
}

The condition above assumes that some database instance contains an array of suspended usernames and the (*array) type case reassures that the right operand is actually the array of values.

# Like

  • Type: ConditionObject

Check if the left operand is similar to the defined pattern in the right operand. The pattern should contain a string where the * asterisk represents any characters.

{
    "Condition": {
        "Like": {
            "${OS.version}": "19.0.*"
        }
    }
}

FYI! The pattern does not really have to have the * asterisk in it. If none specified, then the condition behaves similarly to the Equals condition type where it compares two string.

The right operand may contain a single scalar value or array of scalar values. In the second case, the condition evaluates to true if the left operand is similar or equals to at least one value in the array.

{
    "Condition": {
        "Like": {
            "access": [
                "ac*ss",
                "role"
            ]
        }
    }
}

The above condition is evaluated to true because the access string matches the ac*ss pattern.

# NotLike

  • Type: ConditionObject

Check if the left operand is not similar to the defined pattern in the right operand. The pattern should contain a string where the * asterisk represents any characters.

{
    "Condition": {
        "NotLike": {
            "${ENV.environment}": "staging-*"
        }
    }
}

FYI! The pattern does not really have to have the * asterisk in it. If none specified, then the condition behaves similarly to the NotEquals condition type where it compares two string.

The right operand may contain a single scalar value or array of scalar values. In the second case, the condition evaluates to true if the left operand is not similar or equals to all values in the array.

{
    "Condition": {
        "NoLike": {
            "${USER.name}": [
                "John*",
                "Mel*a"
            ]
        }
    }
}

# RegEx

  • Type: ConditionObject

Check if the left operand matches the regular expression defined in the right operand.

{
    "Condition": {
        "RegEx": {
            "${ORDER.uid}": "^PO[\\d]{10}$"
        }
    }
}

In case you need to define a regular expression's flag(s), you can wrap the regex with two forward slashes / trailing with one or more flags (e.g. g, i, m, etc.). For example, the following condition defines a case-insensitive match for the value that may contain multiple newlines.

{
    "Condition": {
        "RegEx": {
            "${CUSTOMER.full_name}": "/^[\\w\\'-]+$/im"
        }
    }
}