Modeling Building Blocks

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 ServiceAccounts, or distinguish external users from InternalUsers.

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.

Get started with Oso Cloud →