Common Patterns

Common Authorization Patterns in Polar

This page contains a selection of patterns that commonly appear in real-world policies. You can add many of these patterns to your policy with the click of a button via the Rules (opens in a new tab) Workbench. You can also use these patterns as a starting point when writing Polar code and you're never locked into them; the building blocks these patterns comprise are flexible enough to accommodate whatever logic your application may need.

We will follow our usual model of GitCloud — a source code collaboration platform modeled after GitHub and GitLab. See the example application here (opens in a new tab).

Multitenancy

Your app may host many companies' data. Much of that data will be resources, like documents or code. In that case, it's essential to restrict access to only members of the organization that those resources belong to.

You associate users with one or many organizations through roles, and grant permissions to those roles, like "read" if "member".

Oso Policy


actor User { }
resource Organization {
roles = ["admin", "member"];
permissions = [
"read", "add_member", "repository.create",
"repository.read", "repository.delete"
];
# role hierarchy:
# admins inherit all member permissions
"member" if "admin";
# org-level permissions
"read" if "member";
"add_member" if "admin";
# permission to create a repository
# in the organization
"repository.create" if "admin";
# permissions on child resources
"repository.read" if "member";
"repository.delete" if "admin";
}
test "org members can read organizations, and read repositories for organizations" {
setup {
has_role(User{"alice"}, "member", Organization{"acme"});
}
assert allow(User{"alice"}, "read", Organization{"acme"});
assert allow(User{"alice"}, "repository.read", Organization{"acme"});
assert_not allow(User{"alice"}, "repository.delete", Organization{"acme"});
assert_not allow(User{"alice"}, "read", Organization{"foobar"});
}

Sharing

Grant access on a resource to a specific person.

To achieve this, we'll define roles on that resource. We'll often also want to use roles to control who is allowed to share a resource.

Oso Policy


actor User { }
resource Repository {
roles = ["reader", "admin"];
permissions = ["read", "invite"];
"read" if "reader";
"invite" if "admin";
}
test "admin can invite readers" {
setup {
has_role(User{"alice"}, "admin", Repository{"anvil"});
has_role(User{"bob"}, "reader", Repository{"anvil"});
}
assert allow(User{"alice"}, "invite", Repository{"anvil"});
assert allow(User{"bob"}, "read", Repository{"anvil"});
}

Ownership

Grant additional permissions to the "owner" of a resource.

This might be some fixed piece of application-specific data, like the person who opened an issue, or wrote a comment.

Oso Policy


actor User { }
resource Repository {
roles = ["maintainer"];
}
resource Issue {
roles = ["reader", "admin"];
permissions = ["read", "comment", "update", "close"];
relations = { repository: Repository, creator: User };
# repository maintainers can administer issues
"admin" if "maintainer" on "repository";
"reader" if "admin";
"reader" if "creator";
"read" if "reader";
"comment" if "reader";
"update" if "creator";
"close" if "creator";
"close" if "admin";
}
test "issue creator can update and close issues" {
setup {
has_relation(Issue{"537"}, "repository", Repository{"anvil"});
has_relation(Issue{"42"}, "repository", Repository{"anvil"});
has_relation(Issue{"537"}, "creator", User{"alice"});
}
assert allow(User{"alice"}, "close", Issue{"537"});
assert allow(User{"alice"}, "update", Issue{"537"});
assert_not allow(User{"alice"}, "close", Issue{"42"});
}
test "repository maintainers can close issues" {
setup {
has_relation(Issue{"537"}, "repository", Repository{"anvil"});
has_relation(Issue{"42"}, "repository", Repository{"anvil"});
has_relation(Issue{"537"}, "creator", User{"alice"});
has_role(User{"bob"}, "maintainer", Repository{"anvil"});
}
assert allow(User{"bob"}, "close", Issue{"537"});
assert_not allow(User{"bob"}, "update", Issue{"537"});
assert allow(User{"bob"}, "close", Issue{"42"});
}

Groups

Grant access to entire groups of users, rather than assigning them individually.

Oso Policy


actor User { }
# A group is a kind of actor
actor Group { }
resource Repository {
roles = ["reader"];
permissions = ["read"];
"read" if "reader";
}
# users inherit roles from groups
has_role(user: User, role: String, resource: Resource) if
group matches Group and
has_group(user, group) and
has_role(group, role, resource);
test "group members can read repositories" {
setup {
has_role(Group{"anvil-readers"}, "reader", Repository{"anvil"});
has_group(User{"alice"}, Group{"anvil-readers"});
has_group(User{"bob"}, Group{"anvil-readers"});
has_group(User{"charlie"}, Group{"anvil-readers"});
}
assert allow(User{"alice"}, "read", Repository{"anvil"});
assert allow(User{"bob"}, "read", Repository{"anvil"});
assert allow(User{"charlie"}, "read", Repository{"anvil"});
}

Files/Folders

If you're granted access to a folder, by default you'll often also have access to that folder's contents. In this example, anyone with a role on a folder also has the same role on any file/folder it contains.

Oso Policy


actor User { }
resource Repository {
roles = ["reader", "maintainer"];
}
resource Folder {
roles = ["reader", "writer"];
relations = {
repository: Repository,
folder: Folder,
};
"reader" if "reader" on "repository";
"writer" if "maintainer" on "repository";
role if role on "folder";
}
resource File {
permissions = ["read", "write"];
roles = ["reader", "writer"];
relations = {
folder: Folder,
};
role if role on "folder";
"read" if "reader";
"write" if "writer";
}
test "folder roles apply to files" {
setup {
has_role(User{"alice"}, "reader", Repository{"anvil"});
has_relation(Folder{"python"}, "repository", Repository{"anvil"});
has_relation(Folder{"tests"}, "folder", Folder{"python"});
has_relation(File{"test.py"}, "folder", Folder{"tests"});
}
assert allow(User{"alice"}, "read", File{"test.py"});
}

Org Charts

Grant users access to resources based on their relationship to other users, such as their manager or their direct reports.

Oso Policy


actor User {
relations = {
manager: User,
};
}
resource Repository {
roles = ["viewer"];
permissions = ["read"];
relations = { creator: User };
"viewer" if "creator";
"viewer" if "manager" on "creator";
"read" if "viewer";
}
test "manager can have viewer role on employees repos" {
setup {
has_relation(Repository{"acme"}, "creator", User{"alice"});
has_relation(User{"alice"}, "manager", User{"bob"});
}
assert has_role(User{"bob"}, "viewer", Repository{"acme"});
assert allow(User{"bob"}, "read", Repository{"acme"});
}

Custom Roles

Allow users to create new roles with customizable permissions.

Oso Policy


actor User { }
actor Role { }
resource Organization {
roles = ["admin", "member"];
permissions = [
"read", "add_member", "repository.create",
"repository.read", "repository.delete"
];
# role hierarchy:
# admins inherit all permissions that members have
"member" if "admin";
# org-level permissions
"read" if "member";
"add_member" if "admin";
# permission to create a repository in the organization
"repository.create" if "admin";
}
# A custom role is defined by the permissions it grants
has_permission(actor: Actor, action: String, org: Organization) if
role matches Role and
has_role(actor, role, org) and
grants_permission(role, action);
resource Repository {
permissions = ["read", "delete"];
roles = ["member", "admin"];
relations = {
organization: Organization,
};
# inherit all roles from the organization
role if role on "organization";
# admins inherit all member permissions
"member" if "admin";
"read" if "member";
"delete" if "admin";
"read" if "repository.read" on "organization";
"delete" if "repository.delete" on "organization";
}
test "custom roles grant the permissions they are assigned" {
setup {
# repository admins can create + delete repositories
# but don't have full admin permissions on the organization
grants_permission(Role{"repo-admin"}, "repository.read");
grants_permission(Role{"repo-admin"}, "repository.create");
grants_permission(Role{"repo-admin"}, "repository.delete");
has_role(User{"alice"}, Role{"repo-admin"}, Organization{"acme"});
has_relation(Repository{"anvil"}, "organization", Organization{"acme"});
}
assert allow(User{"alice"}, "repository.create", Organization{"acme"});
assert allow(User{"alice"}, "read", Repository{"anvil"});
assert allow(User{"alice"}, "delete", Repository{"anvil"});
assert_not allow(User{"alice"}, "add_member", Organization{"acme"});
}

Default Roles

Let users configure what role users should inherit by default.

Oso Policy


actor User {}
resource Organization {
roles = ["member", "admin"];
permissions = ["set_default_role"];
"set_default_role" if "admin";
}
resource Repository {
roles = ["reader", "editor", "admin"];
permissions = ["write"];
relations = { organization: Organization };
"write" if "editor";
}
has_role(actor: Actor, role: String, repo: Repository) if
org matches Organization and
has_relation(repo, "organization", org) and
has_default_role(org, role) and
has_role(actor, "member", org);
test "default org role grants permission to org members" {
setup {
has_default_role(Organization{"acme"}, "editor");
has_role(User{"alice"}, "member", Organization{"acme"});
has_relation(Repository{"anvil"}, "organization", Organization{"acme"});
}
assert has_role(User{"alice"}, "editor", Repository{"anvil"});
assert allow(User{"alice"}, "write", Repository{"anvil"});
}

Public or Private Resources

A "public" attribute on a resource can be used to indicate that it is public.

Oso Policy


actor User { }
resource Repository {
permissions = ["read"];
"read" if is_public(resource);
}
test "public repositories" {
setup {
is_public(Repository{"anvil"});
}
assert allow(User{"alice"}, "read", Repository{"anvil"});
}

Toggles

A toggle on a resource can conditionally turn on/off default role inheritance.

Oso Policy


actor User { }
resource Organization {
roles = ["admin", "member"];
permissions = [
"read", "add_member", "repository.create",
];
# role hierarchy:
# admins inherit all member permissions
"member" if "admin";
# org-level permissions
"read" if "member";
"add_member" if "admin";
# permission to create a repository
# in the organization
"repository.create" if "admin";
}
resource Repository {
permissions = ["read", "delete"];
roles = ["member", "admin"];
relations = {
organization: Organization,
};
"admin" if "admin" on "organization";
# admins inherit all member permissions
"member" if "admin";
"read" if "member";
"delete" if "admin";
}
# like `role if role on "organization"`
# but with an additional condition `is_protected`
has_role(actor: Actor, role: String, repository: Repository) if
not is_protected(repository) and
org matches Organization and
has_relation(repository, "organization", org) and
has_role(actor, role, org);
test "org members can only read repositories that are not protected" {
setup {
has_role(User{"alice"}, "member", Organization{"acme"});
has_relation(Repository{"anvil"}, "organization", Organization{"acme"});
has_relation(Repository{"bar"}, "organization", Organization{"acme"});
is_protected(Repository{"bar"});
has_relation(Repository{"foo"}, "organization", Organization{"acme"});
is_protected(Repository{"foo"});
# grant alice explicit access to foo
has_role(User{"alice"}, "member", Repository{"foo"});
}
assert has_role(User{"alice"}, "member", Repository{"anvil"});
assert allow(User{"alice"}, "read", Repository{"anvil"});
assert_not allow(User{"alice"}, "read", Repository{"bar"});
assert allow(User{"alice"}, "read", Repository{"foo"});
}
test "org admins can unconditionally read and delete repositories" {
setup {
has_role(User{"alice"}, "admin", Organization{"acme"});
has_relation(Repository{"anvil"}, "organization", Organization{"acme"});
is_protected(Repository{"anvil"});
}
assert allow(User{"alice"}, "read", Repository{"anvil"});
assert allow(User{"alice"}, "delete", Repository{"anvil"});
}

Global Roles

Give users roles that span the entire application (regardless of resource).

Oso Policy


actor User { }
global {
roles = ["admin"];
}
resource Organization {
roles = ["admin", "member", "internal_admin"];
permissions = ["read", "write"];
# internal roles
"internal_admin" if global "admin";
"read" if "internal_admin";
"member" if "admin";
"read" if "member";
"write" if "admin";
}
test "global admins can read all organizations" {
setup {
has_role(User{"alice"}, "admin");
}
assert allow(User{"alice"}, "read", Organization{"acme"});
assert allow(User{"alice"}, "read", Organization{"foobar"});
}

Impersonation

Users can impersonate other users to inherit some subset of the permissions that the other user has.

Oso Policy


actor User {
permissions = ["impersonate"];
"impersonate" if global "support";
}
global {
roles = ["support"];
}
resource Organization {
roles = ["admin", "member"];
permissions = ["read", "write"];
"member" if "admin";
"read" if "member";
"write" if "admin";
}
# a user can do anything some other user can do
# if they are allowed to impersonate that user and
# are currently impersonating them
allow(user: User, action: String, resource: Resource) if
other_user matches User and
has_permission(user, "impersonate", other_user) and
is_impersonating(user, other_user) and
has_permission(other_user, action, resource);
# we need to specify the default allow rule here
# because we added our own custom one above
allow(user: User, action: String, resource: Resource) if
has_permission(user, action, resource);
test "global support users can read user organizations via impersonation" {
setup {
has_role(User{"alice"}, "support");
has_role(User{"bob"}, "admin", Organization{"acme"});
has_role(User{"charlie"}, "member", Organization{"bar"});
is_impersonating(User{"alice"}, User{"bob"});
}
# bob can read as a member
assert allow(User{"bob"}, "read", Organization{"acme"});
# alice can impersonate bob
assert allow(User{"alice"}, "impersonate", User{"bob"});
# alice can read via Bob by impersonating bob
assert allow(User{"alice"}, "read", Organization{"acme"});
# charlie can read as a member
assert allow(User{"charlie"}, "read", Organization{"bar"});
# alice cannot read because alice is not impersonating charlie
assert_not allow(User{"alice"}, "read", Organization{"bar"});
}

Entitlements

Users' permissions are determined by the features they have paid for.

Oso Policy


actor User { }
resource Organization {
roles = ["admin", "member"];
permissions = ["repository.create"];
"member" if "admin";
}
resource Plan {
roles = ["subscriber"];
relations = { subscribed_organization: Organization };
"subscriber" if role on "subscribed_organization";
}
resource Feature {
relations = { plan: Plan };
}
has_permission(user: User, "repository.create", org: Organization) if
has_role(user, "member", org) and
has_quota_remaining(org, Feature{"repository"});
has_quota_remaining(org: Organization, feature: Feature) if
quota matches Integer and
has_quota(org, feature, quota) and
used matches Integer and
quota_used(org, feature, used) and
used < quota;
has_quota(org: Organization, feature: Feature, quota: Integer) if
plan matches Plan and
has_relation(plan, "subscribed", org) and
plan_quota(plan, feature, quota);
declare plan_quota(Plan, Feature, Integer);
declare quota_used(Organization, Feature, Integer);
plan_quota(Plan{"pro"}, Feature{"repository"}, 10);
plan_quota(Plan{"basic"}, Feature{"repository"}, 0);
test "members can create repositorys if they have quota" {
setup {
quota_used(Organization{"apple"}, Feature{"repository"}, 5);
quota_used(Organization{"netflix"}, Feature{"repository"}, 10);
quota_used(Organization{"amazon"}, Feature{"repository"}, 0);
has_relation(Plan{"pro"}, "subscribed", Organization{"apple"});
has_relation(Plan{"pro"}, "subscribed", Organization{"netflix"});
has_relation(Plan{"basic"}, "subscribed", Organization{"amazon"});
has_role(User{"alice"}, "member", Organization{"apple"});
has_role(User{"bob"}, "member", Organization{"netflix"});
has_role(User{"charlie"}, "member", Organization{"amazon"});
}
assert has_quota_remaining(Organization{"apple"}, Feature{"repository"});
# Apple has quota remaining, so all good
assert allow(User{"alice"}, "repository.create", Organization{"apple"});
# Netflix has used all quota
assert_not allow(User{"bob"}, "repository.create", Organization{"netflix"});
# Amazon doesn't have any quota left
assert_not allow(User{"charlie"}, "repository.create", Organization{"amazon"});
}

Talk to an Oso Engineer

If you'd like to learn more about using Oso Cloud in your app or have any questions about this guide, schedule a 1x1 with an Oso engineer. We're happy to help.

Get started with Oso Cloud →