Skip to content

Latest commit

 

History

History

fastapi

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Setting up the Starter Policy

This application assumes the following Polar policy is active:

actor User {}

resource Organization {
  roles = ["viewer", "owner"];
  permissions = ["view", "edit"];

  "view" if "viewer";
  "view" if "owner";
  "edit" if "owner";
}

resource Repository {
  roles = ["viewer", "owner"];
  permissions = ["view", "edit"];
  relations = { repository_tenant: Organization };

  "view" if "viewer";
  "view" if "owner";
  "edit" if "owner";
  "view" if "viewer" on "repository_tenant";
  "view" if "owner" on "repository_tenant";
  "edit" if "owner" on "repository_tenant";
}

Running the sample application

Install oso-sdk from PyPI with the fastapi extra:

pip install --upgrade 'oso-sdk[fastapi]`

Install uvicorn from PyPI to run the webserver:

pip install --upgrade 'uvicorn[standard]`

Grab an API key from your Oso Cloud environment, and update the source code with your key:

# remember to update this before running the server!
API_KEY = "<please provide your api key here>"

Now you can run the webserver with uvicorn:

uvicorn sample_application:app

Making Requests

To authenticate requests, Oso needs to know which Actor is performing an action.

To keep the implementation of this sample application simple, all requests assume the actor is User{"anonymous"}. This is configured with the identify_user_from_request decorator:

@oso.identify_user_from_request
async def user(request: Request) -> str:
  return "anonymous"

So, this request is authenticated as the User{"anonymous"} actor:

curl localhost:8000/org/acme

You can use whatever HTTP client you like for these requests - for these examples, we'll be using curl.

Configuring Authorization Data with Facts

By default, all routes will return a 404 for all users. To get a successful 200 OK response, you'll need to add some facts to Oso Cloud.

Assigning Roles on an Organization

The Organization resource has two roles:

  • the viewer role (which grants view), and
  • the owner role (which grants both view and edit)

By assigning roles to an Actor as a fact, we can authorize access. Roles are assigned to individual instances of a resource - in this case, we'll consider Organization{"acme"}.

GET /org/acme

The get_organization route is enforced:

@app.get("/org/{id}")
@oso.enforce("{id}", "view", "Organization")

To access this route, you'll need to provide the view permission. This can be accomplished by adding a fact, which gives User{"anonymous"} the viewer role:

  • has_role(User{"anonymous"}, "viewer", Organization{"acme"})

Now, the following request should succeed:

curl localhost:8000/org/acme

POST /org/acme

The post_organization route is enforced:

@app.post("/org/{id}")
@oso.enforce("{id}", "edit", "Organization")

This time, instead of the view permission, you'll need to provide the edit permission.

This can be accomplished by adding a fact which gives User{"anonymous"} the owner role:

  • has_role(User{"anonymous"}, "owner", Organization{"acme"})

Now, the following request should succeed:

curl -X POST localhost:8000/org/acme

(This route doesn't require a request body, it is just looking for a POST request.)

Constructing a Relation with Facts

The resource block definition for Repository contains a relation, repository_tenant:

resource Repository {
  # ...
  relations = { repository_tenant: Organization };
  # ...
}

This allows associating a Repository with an Organization. We declare this with a fact, which references:

  • a specific instance of a Repository, and
  • a specific instance of an Organization.

For example, if we wanted to declare a relationship from Repository{"code"} to Organization{"acme"}, we could add the following fact to Oso Cloud:

  • has_relation(Repository{"code"}, "repository_tenant", Organization{"acme"})

This is relevant for a few permissions, defined on the Repository resource:

resource Repository {
  # ...
  "view" if "viewer" on "repository_tenant";
  "edit" if "owner" on "repository_tenant";
  # ...
}

Assuming we declared the relationship between Repository{"code"} and Organization{"acme"}, this means:

  • if an actor has the viewer role on Organization{"acme"}, they will have the view permission on Repository{"code"}
  • if an actor has the owner role on Organization{"acme"}, they will have the edit permission on Repository{"code"}

This gives us some flexbility - we can allow access with a few different approaches.

GET /repo/code

The get_repository route is enforced:

@app.get("/repo/{id}")
@oso.enforce("{id}", "view", "Repository")

To access this route, you'll need to provide the view permission.

We can allow access by assigning the viewer role to User{"anonymous"} on Organization{"acme"}:

  • has_role(User{"anonymous"}, "viewer", Organization{"acme"})

Even though this fact does not reference Repository{"code"}, assuming the relation mentioned earlier is defined (has_relation(Repository{"code"}, "repository_tenant", Organization{"acme"})), our Polar policy declares that the view permission is implied:

if an actor has the viewer role on Organization{"acme"}, they will have the view permission on Repository{"code"}

After adding these facts, the following request should succeed:

curl localhost:8000/repo/code

POST /repo/code

The post_repository route is enforced:

@app.post("/repo/{id}")
@oso.enforce("{id}", "edit", "Repository")

To access this route, you'll need to provide the edit permission.

We can allow access by assigning the owner role to User{"anonymous"} on Organization{"acme"}:

  • has_role(User{"anonymous"}, "owner", Organization{"acme"})

Again, we're not referencing Repository{"code"}, but assuming the relation mentioned earlier is defined (has_relation(Repository{"code"}, "repository_tenant", Organization{"acme"})), the Polar policy declares that the edit permission is implied:

if an actor has the owner role on Organization{"acme"}, they will have the edit permission on Repository{"code"}

After adding these facts, the following request should succeed:

curl -X POST localhost:8000/repo/code

Additional Resources

Thanks for taking the time to read through this guide! If you're looking for more information on Oso Cloud and this SDK, check out some of the following resources:

If you'd like to get in touch, or need some extra help, check out our Slack!