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 actoractor Group { }resource Repository { roles = ["reader"]; permissions = ["read"]; "read" if "reader";}# users inherit roles from groupshas_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"});}
Inheriting Roles from Other Resources
Using the relations
feature
If two resources have a hierarchical relationship (e.g. parent-child), users commonly inherit roles or permissions on the subordinate resource from their role on the superior resource.
In this case, users often rely on Polar's relations
feature, which often
expresses the relationship between the resources in the subordinate resource's
definition.
A common example of this is files and folders; granting access to a folder typically grants access to the 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"});}
Using longhand rules
If two types of resources have any kind of relationship (hierarchical or otherwise), you can inherit roles on one resource from the other. For example, in a parent-child relationship, users can inherit roles from the child on the parent.
This essentially uses the same convention as the relations
feature (relying on
has_relation
facts in your authorization data), but requires writing out the
rule in longhand.
Oso Policy
actor User {}resource ParentResource { roles = ["admin", "member"]; permissions = ["read", "write"]; "read" if "member"; "write" if "admin"; "member" if "admin";}resource ChildResource { roles = ["admin", "member"]; permissions = ["read", "write"]; relations = { parent: ParentResource }; "read" if "member"; "write" if "admin"; "member" if "admin"; # ChildResource inherits admin from ParentResource "admin" if "admin" on "parent";}# ParentResource inherits member from ChildResourcehas_role(user: User, "member", parent_resource: ParentResource) if child_resource matches ChildResource and has_role(user, "member", child_resource) and has_relation(child_resource, "parent", parent_resource);test "inherit role on parent from child" { setup { has_relation(ChildResource{"anvil"}, "parent", ParentResource{"acme"}); has_role(User{"alice"}, "member", ChildResource{"anvil"}); } assert allow(User{"alice"}, "read", ParentResource{"acme"});}
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 grantshas_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 themallow(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 aboveallow(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 repositories 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.