Add Oso Cloud to Your App
Before going through this guide, make sure you follow the Oso Cloud Quickstart to get your Oso API Key properly set in your environment.
This guide will show you how you can use the Oso Cloud client libraries to perform authorization checks in your app.
Prerequisites
If you've finished the quickstart, you should have an Oso Cloud API Key and a policy that looks like this:
actor User {}
resource Organization {
permissions = ["read"];
roles = ["owner"];
"read" if "owner";
}
resource Repository {
permissions = ["push"];
roles = ["maintainer"];
relations = { parent: Organization };
"maintainer" if "owner" on "parent";
"push" if "maintainer";
}
Constructing an Oso instance
The Oso Cloud libraries work by providing an Oso
class which you configure with your Oso Cloud URL and API key:
from oso_cloud import Oso
oso = Oso(url="https://cloud.osohq.com", api_key=YOUR_API_KEY)
Under the hood, this class wraps an HTTP API and handles converting your application objects to and from json.
Enforcement in a controller
In the quickstart, you used the Oso Cloud CLI to perform authorization checks like this:
$ oso-cloud authorize User:bob read Organization:acme
You can use the Oso
class to perform the same check in your application. For example, you
can check whether a user has the "read" permission on an organization whenever they make a GET
request for that organization:
@app.route("/orgs/<org_id>")
def show_org(org_id):
# get the `Organization`
org = db.get_org(org_id)
# get the authenticated user making the request
current_user = get_current_user()
if oso.authorize(current_user, "read", org):
# Action is allowed
return f"<h1>An Organization</h1><p>Welcome to organization {org.name}</p>", 200
else:
return "<h1>Whoops!</h1><p>That organization was not found</p>", 404
Now, when Bob views the /orgs/acme
endpoint, your application asks Oso Cloud to perform the exact same check you did in the
quickstart and only shows him the page if he's authorized to read on the Acme organization.
Updating data
In your policy above, there is a Repository
type that is owned by the Organization
type. A user can read from and push to repositories that belong to their organizations.
For this to work, your application needs some way of telling Oso Cloud which repositories belong to which organizations.
In the CLI, you could do this using oso-cloud tell has_relation
:
$ oso-cloud tell has_relation Repository:anvils parent Organization:acme
Your application can use the Oso
class to do this whenever a user creates a new Repository
(in this case, via a POST
request):
@app.route("/orgs/<org_id>/repos", methods = ['POST'])
def create_repo(org_id):
# get the `Organization`
org = db.get_org(org_id)
# get the authenticated user making the request
current_user = get_current_user()
repo = Repository(name=payload.get("name"), parent_org=org)
db.add_repo(repo)
oso.tell("has_relation", repo, "parent", org)
return f"<h1>Success!</h1><p>Added repository {repo.name} to organization {org.name}</p>", 200
Now, when someone creates a new repository with the /orgs/acme/repos
endpoint, your application will
tell Oso Cloud that Organization:acme
is that repository's parent. This means that Bob (and any other
owners of the Acme organization) will be authorized to push to this repository.
Putting all it together
A more realistic version of the "add a new repository" endpoint will probably check if a user
is authorized to create repositories on that organization. To enable this, you need to
add a create_repo
permission to the Organization
declaration in your policy:
actor User {}
resource Organization {
# add a new `create_repo` permission
permissions = ["read", "create_repo"];
roles = ["owner"];
"read" if "owner";
# grant `create_repo` owners
"create_repo" if "owner";
}
resource Repository {
permissions = ["push"];
roles = ["maintainer"];
relations = { parent: Organization };
"maintainer" if "owner" on "parent";
"push" if "maintainer";
}
Now the "add a new repository" endpoint can perform authorization and update data in Oso Cloud:
@app.route("/orgs/<org_id>/repos", methods = ['POST'])
def create_repo(org_id):
# get the `Organization`
org = db.get_org(org_id)
# get the authenticated user making the request
current_user = get_current_user()
# check that the user can create repositories
if oso.authorize(current_user, "create_repo", org):
repo = Repository(name=payload.get("name"), parent_org=org)
db.add_repo(repo)
oso.tell("has_relation", repo, "parent", org)
return f"<h1>Success!</h1><p>Added repository {repo.name} to organization {org.name}</p>", 200
else:
return "<h1>Whoops!</h1><p>That organization was not found</p>", 404
Talk to an Oso Engineer
Our team is happy to help you get started with Oso. If you'd like to learn more about using Oso in your app or have any questions about this guide, schedule a 1x1 with an Oso engineer.