Polar operators

Operators are used to combine terms in rule bodies into expressions.

Unification (=)

Unification is the basic matching operation in Polar. Two values are said to unify if they are equal or if there is a consistent set of variable bindings that makes them equal. Unification is defined recursively over lists: two lists unify if all of their corresponding elements unify.

The unification operator (=) checks if its left and right operands unify; for example, "a" = "a", x = "a", or ["a", "b"] = [x, "b"] where the variable x is either bound to "a" or unbound.

Conjunction (and)

The and operator is used to state that a pair of conditions in a rule's body must both hold. For example, the rule…

oso_employee(first, last) if
is_user(first, last) and
is_employee("Oso", first, last);

…will be satisfied if the person is a user and an Oso employee.

Disjunction (or)

The or operator will be true if either its left or its right operand is true. Disjunctions can always be replaced by multiple rules with identical heads but different bodies, but the or operator may help simplify writing rules with alternatives. For example:

is_user(first, last) if
oso_employee(first, last) or
is_guest(first, last);
# The `or` can be rewritten as a pair of rules:
is_user(first, last) if oso_employee(first, last);
is_user(first, last) if is_guest(first, last);

Negation (not)

The not operator is used to check that a certain fact does not exist. For example:

allow(user, action, resource) if
not is_banned(user) and
has_permission(user, action, resource);

This rule only allows a user to perform an action on a resource if the policy grants them that permission and the user has not been banned.

As overusing negation can make logic indirect and hard to understand, it is limited to facts; you cannot negate a compound expression (e.g. one containing and or or) nor an expression that refers to another rule in your policy. Additionally, all variables used in a negated fact must also be used in a fact that is not negated.

List membership (in)

The in operator can be used to iterate over lists of strings. An in operation looks like this:

x in ["a", "b", "c"]

The left operand, x, is a variable that will be unified with each element in the list. If the right operand is not a list of strings, the in operation will fail.

In the following example, the variable x will be bound to "a", "b", and "c", in turn, and then the x = "a" check will evaluate. This expression will only succeed for the first item in the list, "a".

x in ["a", "b", "c"] and x = "a"

The left operand does not need to be a variable. For example, the following expression will succeed twice since "a" is in the first and fourth positions in the list:

"a" in ["a", "b", "c", "a"]

Integer comparisons

The <, <=, >, and >= operators compare integer values. For example, if you have a fact expires_at(File:foo, 1670280790) indicating the time that the "foo" file expires:

expires_after_y2k38(resource) if
expires_at(resource, time) and time > 2147483647

matches operator

When writing complex rules you can assert that a variable is valid only for values of a specific type with the matches operator by writing:

<variable> matches <Type>

You typically only want use matches expressions when introducing a new variable in a rule, e.g. to join two rules.

For example, consider this policy snippet:

has_role(user: User, role: String, resource: Resource) if
group matches Group and
has_group(user, group) and
has_role(group, role, resource);

This introduces a variable named group, asserting that it must be of type Group. This rule is satisfied only if both the has_group and has_role facts can be satisfied with the same Group value.

Up next

  • Rules + facts to understand how to write Polar rules using operators.
  • Constants to see runtime-evaluated constants Polar offers

Talk to an Oso Engineer

If you want to learn more about Polar, schedule a 1x1 with an Oso engineer. We're happy to help.

Get started with Oso Cloud →