Resource Blocks

Resource Blocks

Resource blocks provide essential features for building authorization logic. They:

Polar offers two types of resource blocks, which align with its built-in abstract types:

TypeDescription
resourceAn object to which you want to permit certain actions.
actorAn entity to whom you want to grant permissions to perform certain actions.

Each type you define must be declared as an actor or resource.

The simplest form of a block looks like this:


# Actor block
actor User {}
# Resource block
resource Repository {}

A more complete block looks like this:


resource Repository {
permissions = ["read", "push"];
roles = ["contributor", "maintainer"];
relations = { parent: Organization };
# Shorthand rules
## An actor has the "read" permission if they have the "contributor" role.
"read" if "contributor";
## An actor has the "push" permission if they have the "maintainer" role.
"push" if "maintainer";
## An actor has the "contributor" role if they have the "maintainer" role.
"contributor" if "maintainer";
## An actor has the "maintainer" role if they have the "internal_admin" role
## on the "parent" Organization.
"maintainer" if "internal_admin" on "parent";
}

The Global Block

Each Polar policy may have up to one global block, which represents represent roles and permissions that can be referred to by any other resource. These are particularly useful for representing global roles.

The global block has the same syntax as resource blocks, but rather than differentiating blocks by a resource name, there is only a single global block:


global {
roles = ["admin", "member"];
permissions = ["invite_member", "create_tenant"];
"create_tenant" if "member";
"member" if "admin";
"invite_member" if "admin";
}

You can refer to roles and permissions from a global block by using the keyword global before the role/permission you want to use:


resource Organization {
roles = ["internal_admin"];
permissions = ["read"];
"internal_admin" if global "admin";
"read" if "internal_admin";
}

Permissions, roles, and relations

To simplify writing RBAC policies, Polar provides the following features in resource blocks, which you can use when writing shorthand rules.

FeatureDescription
permissionsClassifies actions that Actors can perform on this resource.
rolesClassifies Actors in relation to this resource.
relationsClassifies the relationship between this and others resources.

A few general notes about these features:

  • Each resource block may declare each of these features at most once, i.e. you cannot declare roles twice.
  • Within each resource block, values used among all of these feature must unique. For example, you cannot declare roles with a value "writer" and declare a relations named "writer".
  • The feature must be declared to be available in shorthand rules.

Permissions

Permissions classify actions that Actors can perform on this resource.

permissions declaration values must be an array of Strings. For example:


resource Repository {
permissions = ["read", "push"];
}

Rules that expect permission values will expect String data that matches one of the values you identify in the declaration. For example:


has_permission(actor:Actor, permission:String, resource:Resource)...

Roles

Roles classify Actors in relation to this resource.

roles declarations must be an array of Strings. For example:


resource Repository {
roles = ["contributor", "maintainer", "admin"];
}

Rules and facts that expect roles values will expect String data that matches one of the values you identify in the declaration. For example:


has_role(actor:Actor, role:String, resource:Resource)...

Relations

Roles classify the relationship between this and others resources.

relation declaration must be key-value pairs, whose:

  • Key is an unquoted string (i.e. an identifier)
  • Value is the type of the related object

For example:


resource Repository {
relations = { parent: Organization };
}

Rules and facts that expect relation values will expect String data representing the relation name (e.g. "parent"), as well as data representing the related type (e.g. Organization). For example:


has_relation(resource: Repository, relation: String, parent: Organization)...

Shorthand Rules

If you include permissions, roles, or relations declarations in your resource block, you can also write "shorthand" rules.

For example:


resource Repository {
permissions = ["read", "push"];
roles = ["contributor", "maintainer"];
relations = { parent: Organization };
# Shorthand rules
## An actor has the "read" permission if they have the "contributor" role.
"read" if "contributor";
## An actor has the "push" permission if they have the "maintainer" role.
"push" if "maintainer";
## An actor has the "contributor" role if they have the "maintainer" role.
"contributor" if "maintainer";
## An actor has the "maintainer" role if they have the "internal_admin" role
## on the "parent" Organization.
"maintainer" if "internal_admin" on "parent";
}

A shorthand rule has the basic form:


<role or permission> if <expression>;

The left-hand side of the if statement is the result that is granted if the right-hand side evaluates to true.

Shorthand rules then get expanded into full rules when they're loaded via expansion.

Note that shorthand rules might require additional statements to succeed.

Left-hand side expressions

The left-hand side can be one of:

  • A role, e.g., "contributor".
  • A permission, e.g., "read".
  • The role or permission keywords, which represent any role or permission.

Each of these expand to a rule head, which describes what is being granted. For example, in the shorthand rule "read" if "contributor";, the left-hand side is "read", which expands to:


has_permission(actor: Actor, "read", resource: Repository) if ...

Notice that since the shorthand rule exists in the Repository resource block, we know that the resource type is Repository.

Right-hand side expressions

The right-hand side of the if statement is an expression that describes when the left hand side is true.

Examples of valid right-hand sides are:

  • A role, e.g., "contributor". This is commonly used for granting specific permissions to a role like "read" if "contributor".

  • A permission, e.g., "read". This is typically used to group common permissions together to reduce repetition, e.g., "read.issues" if "read".

  • A role or permission on a related resource, e.g., "reader" on "parent". This is used for granting roles and permissions across resources — that is, granting a role or permission on one type of resource to a role on a related resource. The role/permission being granted must be declared as a role/permission on the related resource type, while the relation must be declared on the current resource type.

  • Rule call syntax, e.g., is_public(resource). This is commonly used to check an attribute on a resource. Supported syntax is a rule name followed by a list of arguments. Arguments can either be primitive types (e.g., strings "open" or numbers 0) or the keyword resource, which refers to the resource declared by the enclosing resource block.

Statements supporting right-hand side expressions

Right-hand side expressions other than rule calls (e.g. roles) require additional statements (i.e. facts or additional rules) to succeed.

The schema of the supporting statements are detailed below as $rhs_pred.

Shorthand Rule Expansion

Shorthand rules are expanded to full Polar rules when they are loaded. The semantics of this expansion are as follows.

Expansion without relation


$lhs if $rhs;
=> $lhs_pred(actor: Actor, $lhs, resource: $Type) if $rhs_pred(actor, $rhs, resource);

The values of $lhs_pred and $rhs_pred (i.e. the expanded predicate) depend on the types of $lhs and $rhs respectively (i.e. the shorthand expression type).

Shorthand expression typeExpanded predicate
Permissionhas_permission
Rolehas_role

The resource argument specializer $Type is determined by the enclosing resource type, e.g. rules defined inside resource Repository {...}, have $Type values of Repository.

Note that for this shorthand rule to allow authorization requests, you might need to include statements with the right-hand side schema (i.e. the expression following the if).

For example:


resource Repository {
permissions = ["read"];
roles = ["contributor"];
"read" if "contributor"; # Shorthand rule
}
# Expanded rule
# "read" if "contributor" ;
# \/ \/
has_permission(actor: Actor, "read", resource: Repository) if has_role(actor, "contributor", resource);

For this rule to succeed, you could include facts like:


has_role(User{"alice"}, "contributor", Repository{"anvils"})

Expansion with relation


$lhs if $rhs on $rel;
=> $lhs_pred(actor: Actor, $lhs, resource: $Type) if
$rhs_pred(actor, $rhs, related) and has_relation(resource, $rel, related);

The values of $lhs_pred and $rhs_pred follow the same semantics as expansion without relation.

$rel must always be a declared relation on the enclosing resource type. The has_relation rule is necessary to access the related object that $rhs_pred references.

Note that for this shorthand rule to allow authorization requests, you might need to include statements with the right-hand side schema (i.e. the expression following the if).

For example:


resource Repository {
roles = ["admin"];
relations = {parent: Organization};
"admin" if "owner" on "parent";
}
# Expanded rule
# "admin" if "owner"
# \/ \/
has_role(actor: Actor, "admin", resource: Repository) if has_role(actor, "owner", related) and
# on "parent" ||
# \/ ||
has_relation(resource, "parent", related);

For this rule to succeed, you could include facts like:


has_role(User{"alice"}, "owner", Organization{"acme"});
has_relation(Repository{"anvils"}, "parent", Organization{"acme"});

Global block expansion

Global blocks expand to reference an instance of the type Global.


$lhs if global $rhs;
=> $lhs_pred(actor: Actor, $lhs, resource: $Type) if $rhs_pred(actor, $rhs);

The values of $lhs_pred and $rhs_pred (i.e. the expanded predicate) depend on the types of $lhs and $rhs respectively (i.e. the shorthand expression type).

Shorthand expression typeExpanded predicate
Permissionhas_permission
Rolehas_role

Note that:

  • For this shorthand rule to allow authorization requests, you might need to include statements with the right-hand side schema (i.e. the expression following the if).
  • The schema of the right-hand side's expanded predicate differs from non-global expansions. global right-hand side predicates do not expect a resource value.

For example:


resource Organization {
roles = ["internal_admin"];
permissions = ["read"];
"internal_admin" if global "admin";
"read" if "internal_admin";
}
# Expanded rule
# "internal_admin" if global "admin" ;
# \/ \/
has_role(actor: Actor, "internal_admin", organization: Organization) if has_role(actor, "admin");

For this rule to succeed, you could include facts like:


has_role(User{"alice"}, "admin");

Referencing resources

Whether your resources are identified as actor or resource, you refer to them using their name (e.g. ResourceName), followed by a literal String value (e.g. "ident") surrounded by curly brackets ({}), e.g.


ResouceName{"ident"}

This is referred to as the "object literal" syntax.

Polymorphism

Polar uses polymorphism widely within resource blocks.

For example, the Actor abstract type used alongside the has_role and has_permission predicates can successfully resolve values of any type declared in an actor resource block.

Additionally, Actor is a subtype of Resource, which means that any variable of type Resource can also be of type Actor. This is why we refer to them as "resource blocks" and not "actor or resource blocks"; all actor types are implicitly also resource types.

For more information, see our extends documentation.

actor vs. resource

Polar uses the convention mentioned above: actor types represent things to which you grant permissions and resource types are those you grant permissions to. However, actor types are resource types, so things get a little confusing.

One guiding principle to use is determining whether something should be an actor or a resource is based on the fully-expanded shorthand rules detailed above. Both has_permission and has_role take a type that extends the Actor abstract type as its first argument. If you can conceive of the resource as a thing that can have roles and permissions assigned to it, modeling it as an actor is a good idea; otherwise, model it as a resource.

Up next

  • Rules + facts to understand how Polar can evaluate your data.
  • Tests to generate unit tests for your resource blocks.

Talk to an Oso Engineer

If you'd like to learn more about resource blocks and how to use them within your policy, schedule a 1x1 with an Oso engineer. We're happy to help.

Get started with Oso Cloud →