Resource Blocks
Actor and resource blocks provide a way to organize authorization logic by application type. These blocks are especially useful for expressing role-based access control logic.
The simplest form of a block looks like this:
# Actor blockactor User {}# Resource blockresource Repository {}
Inside of a block, you can declare permissions, roles, and relations and write shorthand rules.
A more complete block looks like this:
resource Repository { permissions = ["read", "push"]; roles = ["contributor", "maintainer"]; relations = { parent: Organization }; # 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 "owner" role on the "parent" Organization. "maintainer" if "owner" on "parent";}
Once you have declared a block, you can use the built-in Actor
and
Resource
specializers to match all types
declared as actors or resources, respectively.
Permission Declarations
You can specify the permissions that are available on a type of actor or resource using the following syntax:
resource Repository { permissions = ["read", "push"];}
Permissions are always strings. You must declare permissions in order to use them in shorthand rules.
Role Declarations
You can specify the roles that are available for an actor or resource type using the following syntax:
resource Repository { roles = ["contributor", "maintainer", "admin"];}
Roles are always strings. You must declare roles in order to use them in shorthand rules.
Relation Declarations
You can specify relations between actor/resource types using the following syntax:
resource Repository { relations = { parent: Organization };}
Relations are key: value
pairs where the key is the relation name and the
value is the type of the related object. All related objects referenced in a
relation declaration must have an actor or resource block. For example, in the
above snippet, there must be a corresponding resource Organization {}
block
since Organization
is referenced in Repository
's relations.
Shorthand Rules
Shorthand rules are concise rules that you can define inside actor and resource blocks using declared permissions, roles, and relations.
For example,
resource Repository { permissions = ["read", "push"]; roles = ["contributor", "maintainer", "owner"]; relations = { parent: Organization }; # 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 "owner" role on the "parent" Organization. "maintainer" if "owner" on "parent"; # An actor has any permission on a repository if they are the owner permission if "owner"; # All actors can be readers of public repositories "reader" if is_public(resource);}
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
.
Left-hand side expressions
The left-hand side can be one of:
- A role, e.g.,
"contributor"
. - A permission, e.g.,
"read"
. - The
role
orpermission
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.issues"
. 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. And the role/permission 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 numbers0
) or the keywordresource
, which refers to the resource declared by the enclosing resource block.
The Global block
You can use a global block to represent roles and permissions that apply across the entire application.
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";}
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
$x if $y;=> rule1(actor: Actor, $x, resource: $Type) if rule2(actor, $y, resource);
where rule1
and rule2
are the expansions of $x
and $y
respectively.
If $x
is a permission, then rule1
will be
has_permission
. If $x
is a role, then rule1
will be
has_role
. The same semantics apply for $y
and $rule2
.
The resource argument specializer $Type
is determined by the enclosing block
definition. E.g., if the rule is defined inside of
resource Repository {}
, then $Type
will be Repository
.
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);
Expansion with relation
$x if $y on $z;=> rule1(actor: Actor, $x, resource: $Type) if rule2(actor, $y, related) and has_relation(resource, $z, related);
where rule1
, rule2
, and has_relation
are the expansions of $x
, $y
,
and $z
respectively.
The expansion of $x
to rule1
and $y
to rule2
follow the same semantics
as expansion without relation. $z
must always
be a declared relation on the enclosing resource
type. The has_relation
rule is necessary in order to access the related
object that rule2
references.
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);
Global block expansion
Global blocks expand to reference an instance of the type Global
.
$x if global $y;=> rule1(actor: Actor, $x, resource: $Type) if rule2(actor, $y);
where rule1
and rule2
are the expansions of $x
and $y
respectively.
If $x
is a permission, then rule1
will be
has_permission
. If $x
is a role, then rule1
will be
has_role
. The same semantics apply for $y
and $rule2
.
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");
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.