- Modeling an authorization policy
- Declaring authorization as facts’
- Enforcing authorization decisions
User:alice
allowed to "read"
the Repository:foo
repository?”
Then, we model
an authorization policy for Oso Cloud using
common Polar building blocks.
Finally, we’ll see how to maintain
authorization data in Oso Cloud via fact management.
Initial setup
First we need to install Oso Cloud SDKs for GitCloud’s two services__init__.py
app.js
authorize
, list
, and actions
enforcement APIs. This will avoid
some repetitiveness across the various controllers we have to update:
oso_cloud.Value
objects that Oso Cloud accepts:
Enforcement
Next, we enforce authorization across both services. Enforcement happens in the logic reacting to the authorization decision in Oso. For example in a HTTP application,403 Forbidden
is returned when the
authorization decision is False
. 200 OK
is returned when the authorization decision
is True
.
We begin with enforcement to implement Oso Cloud incrementally and observe the
authorization results change as we progress.
Testing your enforcement setup
When implementing authorization enforcement for the first time, it can be
helpful to confirm that the integration is working as expected before
introducing more policy logic.We’ll do that by implementing a policy that allows everything, then one that
denies everything, then observe how the app responds.Oso Cloud’s default (empty) policy denies everything which should return To push a “deny everything” policy:Some authorization systems support both Here, deny is just a placeholder—like is_banned(actor) or any other condition that should override normal access.
true
.With the “allow everything” policy, every request for an access-controlled
resource should return true
.With the “deny everything” (or empty) policy, every request should return
false
.If your app behaves this way, it means enforcement is working correctly and
your authorization API calls are implemented properlyImportant: This is a testing-only strategy. It should never be used in production environments. Always remember to replace these placeholder policies with your real authorization logic.To push an “allow everything” policy to Oso Cloud via the CLI for testing:allow(...) if ...;
and deny(...) if ...;
semantics. In Oso Cloud, the idiomatic way to express this is by using an allow rule with negation. For example:Authorizing an action on a specific resource
Most operations in GitCloud involve a user performing an action on a specific resource. For example:- Viewing a repository
- canceling a job
- updating an organization
authorize()
API, which checks if an actor is allowed to perform an action on a specific resource.
For example, here’s an updated handler for viewing a specific
organization:
"read"
the organization.
We use the same pattern for most of the endpoints across both services.
Note that we chose to raise a
NotFound
error instead of Forbidden
. This
ensures that someone who doesn’t have access to the repository cannot tell if
it exists.Authorizing an action on a collection of resources
The second most common operation in GitCloud is the “index” endpoint. It displays a collection of resources, such as organizations that a user belongs to or issues opened on a particular repository. The naïve implementation to add enforcement on an index endpoint loops over the collection of resources loaded from the database. Each iteration then uses Oso Cloud’sauthorize()
API
to request an authorization decision.
Oso Cloud can wrap those authorization
checks into a single efficient list filtering query: the list()
API.
While authorize()
poses the question “can actor
perform action
on resource
?”, list()
asks
“for which resources of type resource_type
can actor
perform action
?”
For a deeper dive into the
list()
endpoint, head over to the list filtering
guide.list()
API behaves similar to authorize()
except that it takes a
resource type, e.g., the "Organization"
type instead of a specific
{"type": "Organization", "id": 1}
instance. As an example of adding enforcement
for a collection of resources, here is the updated index handler for organizations:
"read"
as a
the collection of IDs. That collection of IDs can be used to load organization
objects from the service
database.
Fetching a collection of authorized IDs from Oso
Cloud’s list()
API and then loading those resources from the database is a
pattern that can be reused across various endpoints.
Conditional UI elements
The final enforcement pattern covered in this tutorial is conditionally displaying UI elements based on the current user’s permissions. For example, this pattern might gray out a button if the user isn’t allowed to perform the action represented. Imagine this pattern in action with the GitCloud user interface (UI) that includes a page for respository administrators to add and remove users. Users who aren’t administrators should not see any links to it inside the UI. To fetch the set of actions that the current user is allowed to perform on a repository efficiently , use Oso Cloud’sactions()
API.
Here’s the updated handler for viewing a specific repository:
actions()
will return a list of actions that the
current user is allowed to perform on the repo
repository. We include these
in the JSON response, so that our UI can check for the "manage_members"
permission. Based on the permission, the UI shows or hides the link
to the admin page as necessary.
Modeling
Now that enforcement is implemented across our services, we will model our policy and push it to Oso Cloud.Polar files can be kept in version control. It also support test for rigorous
CI/CD to prevent regressions.
Standard RBAC
Role-based Access Control establishes that a user can perform an action on a resource if they have a role for that resource. We’ll start with a simple RBAC base policy.authorization.polar
"reader"
role on a specific
repository can "read"
that repository. As we add additional roles and
permissions governing access to repositories, we’ll add them to the resource
block. New RBAC rules are declared with the <permission> if <role>;
form.
We employ the
exact same pattern to fill out the base policy for the other access-controlled
types in our app: organizations, issues, jobs, and users.
Resource hierarchies / multitenancy
Hierarchy patterns often emerge in applications like our GitCloud example. One such hierarchy would be an organization having many repositories and a repository having many issues and jobs. In these cases, access flows from parent resources to child resources. If a user has the"member"
role on the ACME organization, the user should have the same access to
one of ACME’s repositories as a user with the "reader"
role on that specific
repository.
The naïve implementation grants each "member"
an explicit "reader"
role on each repository in the ACME org. Instead
of managing so many extra roles, we can define a relationship between the
Organization
and Repository
types. In it, we declare that an actor with
the "member"
role on a
repository’s parent organization can do anything a "reader"
can
do on the repository.
The policy is updated with to reflect this hierarchy.
authorization.polar
Sharing
In our hypothetical GitCloud app, users should be able to invite others to organizations and repositories. Managing the roles of users who have access to a particular organization or repository is another requirement. This is pattern is common and can be implemented with resource ownership in Polar.authorization.polar
Ownership
The final pattern we implement for GitCloud is ownership. For this example, we will implement authorization for closing issues. A user should be able to close any issue they create. Looking at the Ownership in RBAC example, the easiest way to represent ownership is with a role on a resource.authorization.polar
Pushing the policy to Oso Cloud
Finally, we’re ready to push our initial policy, saved asauthorization.polar
, with
the Oso Cloud CLI:
There are many patterns that apply to GitCloud. If you’re looking for a challenge
or to improve your authorization understanding, try cloning the GitCloud
repo and implementing a new pattern from
the Polar documentation.
Data management
The final step of implementing authorization with Oso Cloud is to provide authorization data as facts. In the modeling step, we defined the abstract authorization policy for GitCloud. Now we need to start provide role assignments (User:alice
has the "admin"
role
on Repository:foo
) and relationships (Organization:acme
is the "organization"
of Repository:foo
)
to Oso Cloud.
When we create a new resource in GitCloud, such as a new repository or
organization, we must supply Oso Cloud data about role assignments and relationships
pertaining to the new resource. For example, when a user creates a new repository,
Oso Cloud requires facts that the user has the "admin"
role on that repository
and an "organization"
relationship between the repository and its
parent organization exists.
In Oso Cloud, role assignments persist as has_role
facts of the form has_role(actor, role, resource)
, e.g.,
has_role(User:alice, "admin", Repository:foo)
.
Similarly, relationships are
represented as has_relation
facts of the form has_relation(related_resource, relation, resource)
, e.g., has_relation(Repository:foo, "organization", Organization:acme)
.
To provide the pair of new facts to Oso Cloud when creating a new repository, we’ll
use the Python client’s
batch()
.
If we only needed to create a single fact, we could use the
tell()
API.batch()
API.
batch()
API, the None
argument acts as a wildcard, instructing Oso
Cloud to delete any roles the user may have on the given organization.
Using the
batch()
API is useful when there are multiple roles for a user. If we needed
to delete a single fact, we use the
delete()
API instead.
Additionally, if wildcards are undeed, you can delete
many facts at once with pairing batch()
with delete()
.
Using context facts
Sometimes it is helpful to be able to provide more authorization data to Oso Cloud when requesting an authorization decision. For example, the python GitCloud service is the only service that knows about issues which are far more numerous than repositories or organizations. Do we have to store issue-related facts in Oso Cloud? In situations like these, we can take advantage of context facts. Context facts are considered only for the duration of a single request. Rather than using thetell()
and
delete()
APIs to sync data about issues to Oso Cloud, we can pass context
facts into any of the authorization APIs, including authorize()
and list()
.
Update our authorize()
helper function to fetch authorization-relevant data about issues from
our database and pass it along as context facts:
authorize("close", issue)
in a handler to ensure that the current user
is authorized to "close"
a given issue. Our helper will pass along two critical has_role
and has_relation
facts so that Oso Cloud can make the correct authorization decision.
Context facts are powerful, but they’re better suited for certain situations
than others. Be sure to read more about context
facts before using them in your
application.
Summary
In this tutorial, we covered the three main pieces involved in adding authorization to a real world app with Oso Cloud:- Modeling a policy
- Managing authorization data as facts
- Enforcing authorization decisiosn
authorize()
and list()
APIs to answer
authorization questions posed about specific resources and collections of
resources.
We learned how to lean on Polar to model common authorization
patterns in Oso Cloud. Finally, we learned how to keep authorization data
synchronized in Oso Cloud via fact management APIs.