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 organizations
has_permission(user: User, "view", organization: Organization) if
has_role(user, "member", organization);
# Admins can edit organizations
has_permission(user: User, "edit", organization: Organization) if
has_role(user, "admin", organization);
# Admins inherit all permissions from members
has_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 roles that actors 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 actors 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...