Quickstart for Python

This guide will walk you through your first change to an Oso policy file. There are three steps:

  1. Download a minimal Python starter project that’s already integrated with Oso.
  2. Run the server and visit the app in your browser.
  3. Make a small change to the policy to allow a new type of access.

The Oso Library works best in monolithic applications. If you’re building authorization for more than one service or want to share a policy across multiple applications, read how to get started with Oso Cloud.

1. Clone the repo and install dependencies

First, clone the Python quickstart repo, and install the dependencies:

git clone https://github.com/osohq/oso-python-quickstart.git
cd oso-python-quickstart
pip install -r requirements.txt

2. Run the server

With the dependencies installed, you should be ready to start the server:

FLASK_APP=app.server python -m flask run

If all is well, the server should be listening on port 5000.

Visit http://localhost:5000/repo/gmail in your browser. You should see a successful response, indicating that you have access to the gmail repo.

To see an unsuccessful response, visit http://localhost:5000/repo/react. You’ll see an error: Repo named react was not found. There actually is a repo named react, but you don’t have access to it. Let’s fix that now.

3. Update the policy

In app/main.polar, add the following two lines to define a new “rule.” This rule will allow any “actor” (or user) to perform the "read" action on a repository if that repository is marked as is_public.

main.polar
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";
}

# This rule tells Oso how to fetch roles for a repository
has_role(actor: User, role_name: String, repository: Repository) if
  role in actor.roles and
  role_name = role.name and
  repository = role.repository;

has_permission(_actor: User, "read", repository: Repository) if
  repository.is_public;

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

Restart the server, and again visit http://localhost:5000/repo/react. Now, you’ll see a successful response:

A
200 response from /repo/react

What just happened?

The quickstart server uses an Oso policy to make sure users are allowed to view repos. The call to oso.authorize() in app/server.py performs this check in the repo_show route. If the user does not have access to a repository, an error response is returned to them.

In this case, the repo with the name react is public because of its definition in the app/models.py file, so it should be accessible to everyone. By making the change to app/main.polar, you told Oso to allow users to "read" repositories that have the is_public field set to true.

That way, when you visited the react repo in your browser, Oso determined that the action was permitted!

Check out the full code for the example below:

server.py
from flask import Flask
from oso import Oso, NotFoundError
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 you will authorize. These types can be referenced
# in the policy.
oso.register_class(User)
oso.register_class(Repository)

# Load your policy files.
oso.load_files(["app/main.polar"])

app = Flask(__name__)


@app.route("/repo/<name>")
def repo_show(name):
    repo = Repository.get_by_name(name)

    try:
        oso.authorize(User.get_current_user(), "read", repo)
        return f"<h1>A Repo</h1><p>Welcome to repo {repo.name}</p>", 200
    except NotFoundError:
        return f"<h1>Whoops!</h1><p>Repo named {name} was not found</p>", 404
models.py
from dataclasses import dataclass
from typing import List


@dataclass
class Repository:
    name: str
    is_public: bool = False

    @staticmethod
    def get_by_name(name):
        return repos_db.get(name)


@dataclass
class Role:
    name: str
    repository: Repository


@dataclass
class User:
    roles: List[Role]

    @staticmethod
    def get_current_user():
        return users_db["larry"]


repos_db = {
    "gmail": Repository("gmail"),
    "react": Repository("react", is_public=True),
    "oso": Repository("oso"),
}

users_db = {
    "larry": User([Role(name="admin", repository=repos_db["gmail"])]),
    "anne": User([Role(name="maintainer", repository=repos_db["react"])]),
    "graham": User([Role(name="contributor", repository=repos_db["oso"])]),
}

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.