Polar: Advanced Resource Creation
In this guide, we'll cover using Polar's shorthand rules to implement both RBAC and ReBAC policies.
While the shorthand rules we will cover are a powerful tool in Polar, it's important to realize they exist primarily to simplify writing specific types of policies. Many users still need to write additional rules.
This guide assumes you've gone over:
Role-based access control (RBAC)
Let's start with the following policy in your rules editor (opens in a new tab); this should look familiar from other tutorials.
actor User {}resource Organization {}# Members can view organizationshas_permission(user: User, "view", organization: Organization) if has_role(user, "member", organization);# Admins can edit organizationshas_permission(user: User, "edit", organization: Organization) if has_role(user, "admin", organization);# Admins inherit all permissions from membershas_role(user: User, "member", organization: Organization) if has_role(user, "admin", organization);test "Polar tutorial, longhand rules" { setup { has_role(User{"alice"}, "admin", Organization{"acme"}); has_role(User{"bob"}, "member", Organization{"acme"}); } assert has_permission(User{"alice"}, "edit", Organization{"acme"}); assert_not has_permission(User{"bob"}, "edit", Organization{"acme"});}
This is minimal RBAC policy, with a User
, an Organization
, and a few rules
describing users' access, as determined by their roles.
However, we can describe this RBAC policy more simply using Polar's shorthand
rules, which we can place inside a resource's curly braces ({}
).
For example, this policy is exactly equivalent to the one listed in the Policy tests section.
actor User {}resource Organization { roles = ["admin", "member"]; permissions = ["view", "edit"]; # RBAC –– "permissions" if "role" "edit" if "admin"; "view" if "member"; # Role implication –– "role" if "role" "member" if "admin";}test "Polar testing and iteration demo" { setup { has_role(User{"alice"}, "admin", Organization{"acme"}); has_role(User{"bob"}, "member", Organization{"acme"}); } assert has_permission(User{"alice"}, "edit", Organization{"acme"}); assert_not has_permission(User{"bob"}, "edit", Organization{"acme"});}
In this resource definition, we've defined role
s that actor
s may have on
this resource, as well as permissions
they might ask to be authorized to
perform. All of the following expressions are known as "shorthand rules," which
Polar can interpret and then produce a long-hand version of the rule.
For example, this shorthand rule:
# RBAC –– "permissions" if "role" "edit" if "admin"; "view" if "member";
Gets converted to this longhand rule:
has_permission(actor: Actor, "edit", organization: Organization) if has_role(actor, "admin", organization);
Astute readers might notice that when we defined the rule originally, we defined
the first parameter as user: User
, while the shorthand rule expansion uses
actor: Actor
. If you're curious why, see
Types.
Even though the resource Organization
definition looks very different, the
same tests continue to pass.
Authorization data to support roles
If you look at the longhand rule above, you'll notice the has_role
rule being
mentioned. Where does that come from?
When using roles
in resources, you also need to provide data describing the
roles that actor
s
have.
In this tutorial, you can see that we provide this data in our test
block:
# Role implication –– "role" if "role" "member" if "admin";}test "Polar testing and iteration demo" { setup { has_role(User{"alice"}, "admin", Organization{"acme"}); has_role(User{"bob"}, "member", Organization{"acme"}); } assert has_permission(User{"alice"}, "edit", Organization{"acme"}); assert_not has_permission(User{"bob"}, "edit", Organization{"acme"});}
For more information see the Polar: Shorthand Rules reference.
Relationship-based access control (ReBAC)
It's common for resources to have relationships between one another. For instance, an account might have a parent organization, as well as be owned by a specific user.
Polar provides a shorthand rule to express these relationships, much like it does roles and permissions. However, where those features are designed to provide simplified RBAC policies, relations are built with ReBAC in mind.
actor User {}resource Organization { roles = ["admin", "member"]; permissions = ["view", "edit"]; # RBAC –– "action" if "role" "edit" if "admin"; "view" if "member"; # Role implication –– "role" if "role" "member" if "admin";}resource Account { permissions = ["view", "edit"]; relations = { owner: User, parent: Organization, }; # ReBAC –– "action" if "role" on "resource" "view" if "member" on "parent"; "edit" if "admin" on "parent"; # ReBAC –– "action" if "resource" "edit" if "owner";}
You might notice that Account
doesn't have any of its own roles defined; this
is because it depends on its parent Organization
to supply information about
the roles of users requesting access to it.
relations
used in shorthand rules generate longhand rules. If you ever want to
know the longhand version of a shorthand rule, you can hover over it in the
rules editor and you'll get a small pop-up window with the longhand version. In
the case of:
# ReBAC –– "action" if "role" on "resource" "view" if "member" on "parent"; "edit" if "admin" on "parent";
The longhand rule is:
has_permission(actor: Actor, "view", account: Account) if organization matches Organization and has_relation(account, "parent", organization) and has_role(actor, "member", organization);
Authorization data to support resource relationships
Just as you need to provide authorization data describing your shorthand
roles
, you also need to describe the relationship between your
resources
Here's an example demonstrating the kinds of relationships you would need to include in your authorization data:
test "resource relations" { setup { # Alice owns Alice's account. has_relation(Account{"alice"}, "owner", User{"alice"}); # Alice's account is part of Acme. has_relation(Account{"alice"}, "parent", Organization{"acme"}); # Bob is a member of Acme. has_role(User{"bob"}, "member", Organization{"acme"}); } # Alice can edit her own account, despite not having an explicit # role at Acme. assert has_permission(User{"alice"}, "edit", Account{"alice"}); # Bob can view Alice's account as a member of Acme, which Alice's # account is part of. assert has_permission(User{"bob"}, "view", Account{"alice"});}
For more information see the Polar: Shorthand Rules reference.
Up next
For more details on...
- Modeling in Polar, see Common Authorization Patterns.
- Storing authorization data, see Authorization Data