Model Your Authorization Logic

Authorization in Oso starts with the policy. To model authorization with Oso, you write a policy in Polar - Oso’s declarative policy language. The policy defines the resources that you want to control access to and includes rules governing access to them.

In this guide, we’ll cover how to express your authorization logic using Oso. We’ll use GitClub (our example application) as an example, but you can follow along with your application.

Install Oso

The Oso library evaluates authorization policies.

The Python version of Oso is available on PyPI and can be installed using pip:

$ pip install oso

For more detailed installation instructions, see installation.

Create a Policy

Policies are files that are packaged with the rest of your application code. Oso loads and evaluates policy files when your application runs. Now that you’ve installed Oso, create a policy file called main.polar and use it in your app:

oso.py
from pathlib import Path

from oso import Oso

from .models import User, Repository

# Initialize the Oso object. This object is usually
# used globally throughout an application.
oso = Oso()

# Tell Oso about the data that you will authorize.
# These types can be referenced in the policy.
oso.register_class(User)
oso.register_class(Repository)

# Load your policy file.
oso.load_files([Path(__file__).parent / "main.polar"])

Define Resources, Permissions, and Roles

Authorization controls access to resources in your application. In this guide, we’re going to show how to implement the most common authorization model, role-based access control, with Oso. See the guides section for how to implement other authorization models with Oso.

To start with, you need to define resource blocks in your policy. Let’s say we have an application like GitHub that includes a Repository resource. Define the Repository resource in main.polar:

actor User {}

resource Repository {
    permissions = ["read", "push"];
    roles = ["contributor", "maintainer"];

    # A user has the "read" permission if they have the
    # "contributor" role.
    "read" if "contributor";

    # A user has the "push" permission if they have the
    # "maintainer" role.
    "push" if "maintainer";
}

This policy declares "read" and "push" as permissions for the Repository resource and assigns them to roles. We also tell Oso that our User class is an actor with the actor User {} declaration.

The "read" if "contributor"; statement is an example of a shorthand rule. Add an "admin" role and give it its own permissions by adding some more shorthand rules:

resource Repository {
    permissions = ["read", "push", "delete"];
    roles = ["contributor", "maintainer", "admin"];

    "read" if "contributor";
    "push" if "maintainer";

	# A user has the "delete" permission if they have the
	# "admin" role.
	"delete" if "admin";

	# A user has the "maintainer" role if they have
	# the "admin" role.
    "maintainer" if "admin";

	# A user has the "contributor" role if they have
	# the "maintainer" role.
    "contributor" if "maintainer";
}

The last rules we added are between two roles: A user has the "maintainer" role and all permissions associated with it if they have the "admin" role, and the same for "contributor" and "maintainer".

Give Your Users Roles

Now that we’ve written the core of our policy, we must associate users with roles in our application. Oso doesn’t manage authorization data. The data stays in your application’s existing data store.

Static and dynamic data: All the data we’ve defined so far in the policy is static: it isn’t changed by end users of the application. The development team modifies permission associations with roles and the list of roles for each resource by updating the policy. But, some parts of this policy must be dynamic: the association of users with a role.

Write a has_role rule to tell Oso whether your users have a particular role:

has_role(user: User, role_name: String, repo: Repository) if
  role in actor.roles and
  role_name = role.name and
  repository = role.repository;

This is an example of a full Polar rule. We’ll go more into writing rules in the Write Polar rules.

The has_role rule uses the user object passed into Oso by your application to lookup roles. In this example, Polar will access the roles field on the user object and look up the role names that the user has. Here’s an example Python data model that could be used by this rule. In your application, you’ll likely use your existing User model to maintain this information.

models.py
@dataclass
class Role:
    name: str
    repository: Repository


@dataclass
class User:
    roles: List[Role]


users_db = {
    "larry": User([Role(name="admin", repository=repos_db["gmail"])]),
}

Allow Access

Oso policies have a special rule: the allow rule. The allow rule is the entrypoint to the policy, and is used by the Oso library to check if an actor (the user making a request) can perform an action on a resource.

The resource blocks you wrote define your authorization model. For example, "read" if "contributor" says a user has the "read" permission if they have the "contributor" role.

You can check for this condition by calling the has_permission rule: has_permission(user, "read", repository).

To connect this with the allow entrypoint, you must write the following:

# Allow access if users have the required permission,
# as defined by resource blocks.
allow(actor, action, resource) if
	has_permission(actor, action, resource);

The Complete Policy

actor User {}

resource Repository {
    permissions = ["read", "push", "delete"];
    roles = ["contributor", "maintainer", "admin"];

    "read" if "contributor";
    "push" if "maintainer";
    "delete" if "admin";

    "maintainer" if "admin";
    "contributor" if "maintainer";
}

has_role(actor: User, role_name: String, repository: Repository) if
    role in actor.roles and
    role_name = role.name and
    repository = role.repository;

allow(actor, action, resource) if
    has_permission(actor, action, resource);

What’s Next

Now that we’ve setup our policy, let’s see how we can enforce it!

Connect with us on Slack

If you have any questions, or just want to talk something through, jump into Slack. An Oso engineer or one of the thousands of developers in the growing community will be happy to help.