Skip to main content

Build ABAC conditions

Write the conditions object that defines which users or resources belong to an attribute-based access control (ABAC) condition set. This page is for developers who create condition sets through the Permit API. It shows the shape of a condition, how logical operators nest, and valid and invalid examples. For the full operator list, see ABAC condition operators.

Structure of a condition

A condition set's request body holds its conditions under the conditions key, and the value is an object:

"conditions": {}

A condition is built from three parts:

  • An attribute reference in the form <object>.<attribute>, for example user.location or resource.owner.
  • A comparison operator and its operand, for example {"in": ["US", "Canada"]}.
  • Optional logical operators (allOf, anyOf, not) that combine several conditions.

The object in an attribute reference follows the type of the set. A user set ("type": "userset") reads user.<attribute>, and a resource set ("type": "resourceset") reads resource.<attribute>. The attribute must be defined in your environment before a condition can use it, and the value comes from the user or resource record in Permit, or from the attributes your application passes in the permit.check() call. See Define attributes.

Combine conditions with logical operators

Place a logical operator inside the conditions object. The allOf and anyOf operators take an array of sub-conditions. Each sub-condition is an attribute with a comparison operator, or another logical operator.

allOf: every condition must be true

The allOf operator returns true only when condition A and condition B are true.

allOf example

To match only the users who are located in the US and hold the editor role, put both conditions in allOf. user.roles holds the keys of the roles the user has, so the condition on it uses array_contains.

"conditions": {
"allOf": [
{"user.location": {"in": ["US"]}},
{"user.roles": {"array_contains": "editor"}}
]
}

anyOf: at least one condition must be true

The anyOf operator returns true when condition A or condition B is true.

anyOf example

To match a student who has a math score higher than 80 or an average of 60 or more across all subjects, put both conditions in anyOf.

"conditions": {
"anyOf": [
{"user.math_score": {"greater-than": 80}},
{"user.average_score": {"greater-than-equals": 60}}
]
}

not: invert a condition

The not operator inverts the result of the condition nested inside it. When the nested condition is false, not returns true.

not example

To match every user outside the US, wrap the condition "user.location is in ["US"]" in not.

The value of not is a single nested condition or logical operator object. It doesn't accept an array, and it doesn't accept a primitive value such as a string or a number.

"conditions": {
"not": {
"user.location": {"in": ["US"]}
}
}
Alternative operator keys

You can write the AND and OR operators as allOf and anyOf, or as and and or. Both forms produce the same condition.

Valid and invalid condition examples

You can nest operators in two ways: start with a logical operator and put attributes inside it (logical-first), or start with an attribute and put logical operators inside its comparison (attribute-first). The examples below show both forms, with the reason each invalid example fails.

Logical-first conditions

Valid: nested allOf and anyOf

The following condition is true for paying users who have the editor role or belong to the marketing department, in the US or Canada:

{
"allOf": [
{"user.paying": {"equals": true}},
{
"anyOf": [
{"user.roles": {"array_contains": "editor"}},
{"user.department": {"equals": "marketing"}}
]
},
{"user.location": {"in": ["US", "Canada"]}}
]
}

Invalid: unknown logical operator names

The following condition is invalid because all_of and any_of are not logical operators. Use allOf and anyOf (or and and or).

{
"all_of": [
{"user.paying": {"equals": true}},
{
"any_of": [
{"user.roles": {"array_contains": "editor"}},
{"user.department": {"equals": "marketing"}}
]
},
{"user.location": {"in": ["US", "Canada"]}}
]
}

Valid: logical operators inside an attribute

The following user set condition is true when the check passes an hour between 9 and 12 or between 13 and 18, on any weekday other than Saturday and Sunday. Your application sends current_hour and current_weekday as user attributes in each permit.check() call, the same way the time-based role example sends current_time:

{
"allOf": [
{
"user.current_hour": {
"anyOf": [{"between": [9, 12]}, {"between": [13, 18]}]
}
},
{
"user.current_weekday": {
"not": {"anyOf": [{"equals": "saturday"}, {"equals": "sunday"}]}
}
}
]
}

Valid: two conditions on the same attribute

The following condition is true when user.age is between 15 and 18, or greater than 40. Each item of the or array is a separate attribute condition:

{
"or": [
{"user.age": {"between": [15, 18]}},
{"user.age": {"greater-than": 40}}
]
}

Attribute-first conditions

Valid: and inside an attribute

The following condition is true when user.current_hour is between 9 and 12 and is not 10. Both comparisons inside and must hold for the same attribute value, so two comparisons that can't both be true, such as {"between": [9, 12]} and {"equals": 13}, make the condition impossible to satisfy.

{"user.current_hour": {"and": [{"between": [9, 12]}, {"not-equals": 10}]}}

Invalid: unsupported comparison operator

The following condition is invalid because greater-than-unsupported is not a comparison operator. The API rejects a condition set whose operator key is not in the comparison operator list.

{
"user.age": {
"or": [
{"between": [15, 18]},
{"greater-than-unsupported": 40}
]
}
}

Valid: or inside an attribute

The following condition is the attribute-first form of the same-attribute example: user.age is between 15 and 18, or greater than 40.

{
"user.age": {
"or": [
{"between": [15, 18]},
{"greater-than": 40}
]
}
}

Resource set conditions

A resource set ("type": "resourceset") reads resource.<attribute>, and its conditions use the same shape and the same operators as a user set's conditions. The following resource set condition is true for documents whose status attribute is published and whose owner attribute equals the key of the user in the check. The {"ref": "user.key"} operand compares the resource attribute with an attribute of the user instead of with a fixed value. See Compare two attributes with a reference.

{
"allOf": [
{"resource.status": {"equals": "published"}},
{"resource.owner": {"equals": {"ref": "user.key"}}}
]
}

A resource set also needs the resource_id field in the request body, which names the resource the set narrows. See Create condition sets with the API.

Test a condition

The API validates the shape of a condition when you create the set, and the policy decision point (PDP) evaluates it on each check. Test both:

  1. Create a condition set with the condition, with a POST request to https://api.permit.io/v2/schema/{proj_id}/{env_id}/condition_sets. See Create condition sets with the API. An HTTP 422 response means the condition has an unknown operator or the wrong shape, and the response body names the field that failed.
  2. Read the set back with GET /v2/schema/{proj_id}/{env_id}/condition_sets/{condition_set_id}, and confirm that conditions holds what you sent.
  3. Grant a permission to the set with a condition set rule, so a check can return true through it. Without a rule, a check returns false however well the condition matches.
  4. Run permit.check() against an Edge PDP for a user and resource whose attributes make the condition true, and confirm the check returns true. Then change one attribute so the condition is false, and confirm the check returns false. See Check permissions.
Condition sets need an Edge PDP

The Cloud PDP does not evaluate ABAC condition sets. A check that depends on a condition set returns false there, with the cloud_pdp_not_supporting_abac denial code in the decision log. Send the check to an Edge PDP instead. See Cloud PDP capabilities.

If a check returns false when you expect true, open the decision log for that check: the no_matching_usersets and no_matching_resourcesets codes say which side of the rule didn't match. See Denial codes in decision logs.

Next steps