Building Blocks
No matter how complex your authorization policy, it will be made from a combination of common components we call building blocks.
Once you've learned how to use them, you can construct authorization models tailored to your application's needs.
Actors and resources
All authorization comes down to "who can act on what".
Actors are the who. Most of the time, this is a User
, but you might also
want to model actions taken by internal ServiceAccount
s, or distinguish
external users from InternalUser
s.
In Oso, you add actors to your model with an actor block:
actor User { }
Resources are the what. Typically, resources are your data models (or application objects) that actors are interacting with.
For example, in GitCloud, users interact with the data models: Organization
, Repository
, Issue
, etc.
Most of our authorization models revolve around determining whether a User
is allowed to perform some action
on one of these.
In Oso, you add resources to your model with resource blocks:
resource Repository { ...}
You'll see your policy's actor and resource types show up when adding data to Oso Cloud.
For example, to tell Oso that the actor "alice" (a User
) is an admin, we
use the tell
API:
oso-cloud tell is_admin User:alice
Roles
Roles are groups of permissions that can be assigned to users. Most apps have roles. You've seen roles like "member," "owner," or "admin" in the patterns above and in other apps. In authorization terms, a role is a set of permissions given to a specific actor (like a user) on a specific resource (like a repository). A user with the "member" role may only have "read" and "write" permissions, but an "admin" may also have a "delete" permission.
Roles are a very general building block that you can use to represent many relationships between actors and resources. Most authorization use cases can be modeled using roles, so it's the first building block you should reach for.
In Oso, you'll typically set up roles via resource blocks. Let's look at a sample policy to see how to define roles, permissions, and the relationships between them.
resource Repository { permissions = ["read", "edit", "delete"]; roles = ["reader", "editor", "admin"]; # permission assignments "read" if "reader"; "edit" if "editor"; # give a role all permissions permission if "admin"; # role implications "reader" if "editor";}
Permission and Role Definitions
Define permissions by adding them to resource blocks within your policy.
permissions = ["read", "edit", "delete"];
Define roles in a similar fashion.
roles = ["reader", "editor", "admin"];
Permission assignments
Assign permissions to roles by using the shorthand syntax seen below:
"edit" if "editor";
You can also write the equivalent long-form instead of defining the permission within the resource block.
has_permission(actor: Actor, "edit", repo: Repository) if has_role(actor, "editor", repo);
*Note: Rules in long-form syntax must be defined outside of resource blocks.*
Role implications
Roles can imply other roles, which means that anyone with a role will additionally have all permissions granted by the implied role.
Add role implications by using the shorthand syntax seen below:
"editor" if "admin";
You can also write the equivalent long-form instead of defining the role within the resource block.
has_role(actor: Actor, "editor", repo: Repository) if has_role(actor, "admin", repo);
*Note: Rules in long-form syntax must be defined outside of resource blocks.*
Role assignments
Assigning roles to users is done by adding data to Oso. For example, to assign Alice the reader role on the foo
repo:
oso-cloud tell has_role User:alice reader Repository:foo
In Oso, role assignments use the has_role
fact, which is a built-in
convention in the Polar language. While you hypothetically could assign
roles using your own facts like my_has_role(User:alice, "reader", Repository:foo)
, you would not be able to use resource block syntax to define
permission assignments.
Resource hierarchies
Resources often exist in hierarchies.
In these cases, access flows from parent resources to their subresources.
For example, in GitCloud your permissions on an Issue
are often determined by your role on the parent Repository
.
You create resource hierarchies in Oso by using resource blocks. Let's look at a sample policy to see how.
resource Organization { ...}resource Repository { ... # declares that repositories have an "organization" # relationship with organizations relations = { organization: Organization, }; # role implication using the parent organization "reader" if "member" on "organization"; role if "admin" on "organization";}
The pieces that make up resource hierarchies are:
Assign permissions with relations
Let authorization logic flow through the resource hierarchy by using the shorthand syntax seen above:
"reader" if "member" on "organization";
You can also write the equivalent long-form instead of defining the relationship-based role within the resource block.
has_role(actor: Actor, "reader", repo: Repository) if org matches Organization and has_relation(repo, "organization", org) and has_role(actor, "member", org);
*Note: Rules in long-form syntax must be defined outside of resource blocks.*
Inserting relationship data
Create the hierarchy in Oso by telling us the relevant data:
oso-cloud tell has_relation Repository:anvil organization Organization:acme
Rather than store all relationship data in Oso Cloud, you can also provide it at authorization-time by using Context Facts.
Actor hierarchies
Like resources, actors also have hierarchies. If you're a member of a working group, you'll have all of the permissions that the group has.
Create actor hierarchies by relating multiple actors:
actor User { }actor Group { }has_role(user: User, role: String, resource: Resource) if group matches Group and has_group(user, group) and has_role(group, role, resource);
Attributes
Attributes describe information about actors and resources. An attribute on a particular actor or resource provides information about that actor or resource. Information from attributes on a specific actor and on a specific resource can then be used to determine whether to give the actor a specific permission on that resource.
In Oso, attributes are represented by having arbitrary facts.
You've already seen how to add facts about roles and relationships, but you can tell Oso anything you want (as long as it's used in your policy somewhere)!
For example, we could say that users can only comment on open issues that they can read:
has_permission(user: User, "comment", issue: Issue) if issue_status(issue, "open") and has_permission(user, "read", issue);
Here, the issue_status(Issue, String)
facts are a way to communicate that
attribute about the issue to Oso.
Finally, we need to tell Oso about that piece of data:
oso-cloud tell issue_status Issue:490 open
For attributes that reflect application state, it is often better to keep these stored in the application and provide to Oso Cloud at authorization-time by using Context Facts
However, if you need this application state to be shared between multiple services for authorization, then Oso Cloud is a good place to store the state.
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.