row level security for FastAPI framework

Overview

Row Level Permissions for FastAPI

Build Status

While trying out the excellent FastApi framework there was one peace missing for me: an easy, declarative way to define permissions of users (and roles/groups) on resources. Since I reall love the way Pyramid handles this, I re-implemented and adapted the system for FastApi (well, you might call it a blatant rip-off).

An extremely simple and incomplete example:

from fastapi import Depends, FastAPI
from fastapi.security import OAuth2PasswordBearer
from fastapi_permissions import configure_permissions, Allow, Deny
from pydantic import BaseModel

app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")

class Item(BaseModel):
    name: str
    owner: str

    def __acl__(self):
        return [
            (Allow, Authenticated, "view"),
            (Allow, "role:admin", "edit"),
            (Allow, f"user:{self.owner}", "delete"),
        ]

class User(BaseModel):
    name: str

    def principals(self):
        return [f"user:{self.name}"]

def get_current_user(token: str = Depends(oauth2_scheme)):
    ...

def get_active_user_principals(user:User = Depends(get_current_user)):
    ...

def get_item(item_identifier):
    ...

# Permission is already wrapped in Depends()
Permission = configure_permissions(get_active_user_principals)

@app.get("/item/{item_identifier}")
async def show_item(item: Item=Permission("view", get_item)):
    return [{"item": item}]

For a better example install fastapi_permissions source in an virtual environment (see further below), and start a test server:

(permissions) $ uvicorn fastapi_permissions.example:app --reload

Visit http://127.0.0.1:8000/docs to try it out. There are two users available: "bob" and "alice", both have the password "secret".

The example is derived from the FastApi examples, so it should be familiar. New / added stuff is marked with comments in the source file fastapi_permissions/example.py

Why not use Scopes?

For most applications the use of scopes to determine the rights of a user is sufficient enough. So if scopes fit your application, please use them - they are already a part of the FastAPI framework.

While scopes are tied only to the state of the user, fastapi_permissions also take the state of the requested resource into account.

Let's take an scientific paper as an example: depending on the state of the submission process (like "draft", "submitted", "peer review" or "published") different users should have different permissions on viewing, editing or retracting. This could be acomplished with custom code in the path definition functions, but fastapi_permissions offers a method to define these constraints in a single place.

There is a second case, where fastapi_permissions might be the right addition to your app: If your brain is wired / preconditioned like mine to such a permission model - e.g. exposed for a long time to Pyramid...

Long Story Short: Use scopes until you need something different.

Concepts

Since fastapi_permissions heavely derived from the Pyramid framework, I strongly suggest to take a look at its security documentation if anything is unclear to you.

The system depends on a couple of concepts not found in FastAPI:

  • resources: objects that provide an access controll list
  • access controll lists: a list of rules defining which principal has what permission
  • principal: an identifier of a user or his/her associated groups/roles
  • permission: an identifier (string) for an action on an object

resources & access controll lists

A resource provides an access controll list via it's __acl__ attribute. It can either be an property of an object or a callable. Each entry in the list is a tuple containing three values:

  1. an action: fastapi_permissions.Allow or fastapi_permissions.Deny
  2. a principal: e.g. "role:admin" or "user:bob"
  3. a permission or a tuple thereof: e.g. "edit" or ("view", "delete")

Examples:

from fastapi_permissions import Allow, Deny, Authenticated, Everyone

class StaticAclResource:
    __acl__ =  [
        (Allow, Everyone, "view"),
        (Allow, "role:user", "share")
    ]

class DynamicAclResource:
    def __acl__(self):
        return [
        (Allow, Authenticated, "view"),
        (Allow, "role:user", "share"),
        (Allow, f"user:{self.owner}", "edit"),
    ]

# in contrast to pyramid, resources might be access conroll list themselves
# this can save some typing:

AclResourceAsList = [(Allow, Everyone, "view"), (Deny, "role:troll", "edit")]

You don't need to add any "deny-all-clause" at the end of the access controll list, this is automagically implied. All entries in a ACL are checked in the order provided in the list. This makes some complex configurations simple, but can sometimes be a pain in the lower back…

The two principals Everyone and Authenticated will be discussed in short time.

users & principal identifiers

You must provide a function that returns the principals of the current active user. The principals is just a list of strings, identifying the user and groups/roles the user belongs to:

Example:

def get_active_principals(user: User = Depends(get_current_user)):
    if user:
        # user is logged in
        principals = [Everyone, Authenticated]
        principals.extend(getattr(user, "principals", []))
    else:
        # user is not logged in
        principals = [Everyone]
    return principals

special principals

There are two special principals that also help providing access controll lists: Everyone and Authenticated.

The Everyone principal should be added regardless of any other defined principals or login status, Authenticated should only be added for a user that is logged in.

permissions

A permission is just a string that represents an action to be performed on a resource. Just make something up.

As with the special principals, there is a special permission that is usable as a wildcard: fastapi_permisssions.All.

Usage

There are some things you must provide before using the permissions system:

  • a callable (FastApi dependency) that returns the principal of the logged in (active) user
  • a resource with an access controll list

Configuring the permissions system

Simple configuration with some defaults:

from fastapi_permissions import configure_permissions

# must be provided
def get_active_principals(...):
    """ returns the principals of the current logged in user"""
    ...

# Permission is already wrapped in Depends()
Permission = configure_permissions(get_active_principals)

One configuration option is available:

  • permission_exception:
    • this exception will be raised if a permission is denied
    • defaults to fastapi_permissions.permission_exception
from fastapi_permissions import configure_permissions

# must be provided
def get_active_principals(...):
    """ returns the principals of the current logged in user"""
    ...

# Permission is already wrapped in Depends()
Permission = configure_permissions(
    get_active_principals,
    permission_exception

)

using permissions in path operation

To use access controll in a path operation, you call the perviously configured function with a permission and the resource. If the permission is granted, the requested resource the permission is checked on will be returned, or in this case, the acl list

from fastapi_permissions import configure_permissions, Allow

# must be provided
def get_active_principals(...):
    """ returns the principals of the current logged in user"""
    ...

example_acl = [(Allow, "role:user", "view")]

# Permission is already wrapped in Depends()
Permission = configure_permissions(get_active_principals)

@app.get("/")
async def root(acls:list=Permission("view", example_acl)):
    return {"OK"}

Instead of using an access controll list directly, you can also provide a dependency function that might fetch a resource from a database, the resouce should provide its access controll list via the __acl__ attribute:

from fastapi_permissions import configure_permissions, Allow

# must be provided
def get_active_principals(...):
    """ returns the principals of the current logged in user"""
    ...

# fetches a resource from the database
def get_item(item_id: int):
    """ returns a resource from the database

    The resource provides an access controll list via its "__acl__" attribute.
    """
    ...

# Permission is alredy wrapped in Depends()
Permission = configure_permissions(get_active_principals)

@app.get("/item/{item_id}")
async def show_item(item:Item=Permission("view", get_item)):
    return {"item": item}

helper functions

Sometimes you might want to check permissions inside a function and not as the definition of a path operation:

With has_permission(user_principals, permission, resource) you can preform the permission check programatically. The function signature can easily be remebered with something like "John eat apple?". The result will be either True or False, so no need for try/except blocks \o/.

from fastapi_permissions import (
    has_permission, Allow, All, Everyone, Authenticated
)

user_principals == [Everyone, Authenticated, "role:owner", "user:bob"]
apple_acl == [(Allow, "role:owner", All)]

if has_permission(user_principals, "eat", apple_acl):
    print "Yum!"

The other function provided is list_permissions(user_principals, resource) this will return a dict of all available permissions and a boolean value if the permission is granted or denied:

from fastapi_permissions import list_permissions, Allow, All

user_principals == [Everyone, Authenticated, "role:owner", "user:bob"]
apple_acl == [(Allow, "role:owner", All)]

print(list_permissions(user_principals, apple_acl))
{"permissions:*": True}

Please note, that "permissions:*" is the string representation of fastapi_permissions.All.

How it works

The main work is done in the has_permissions() function, but the most interesting ones (at least for me) are the configure_permissions() and permission_dependency_factory() functions.

Wait. I didn't tell you about the latter one?

The permission() thingy used in the path operation definition before is actually the mentioned permission_dependency_factory(). The configure_permissions() function just provisiones it with some default values using functools.partial. This reduces the function signature from permission_dependency_factory(permission, resource, active_principals_func, permission_exception) down to partial_function(permission, resource).

The permission_dependency_factory returns another function with the signature permission_dependency(Depends(resource), Depends(active_principals_func)). This is the acutal signature, that Depends() uses in the path operation definition to search and inject the dependencies. The rest is just some closure magic ;-).

Or in other words: to have a nice API, the Depends() in the path operation function should only have a function signature for retrieving the active user and the resource. On the other side, when writing the code, I wanted to only specifiy the parts relevant to the path operation function: the resource and the permission. The rest is just on how to make it work.

(F.)A.Q.

Permission check on collection of resources

How to use the library with something like this: List[Item]=Permission("edit", get_items). The question was actually issue #3 and I have written a longer answer in the issue, please have a look there.

Dev & Test virtual environment

There is an easy to use make command for setting up a virtual environment, installing the required packages and installing the project in an editable way.

$ git clone https://github.com/holgi/fastapi-permissions.git
$ cd fastapi-permissions
$ make devenv
$ source .venv/bin/activate

Then you can test any changes locally with make test. This will stop on the first error and not report coverage.

(permissions) $ make test

If you can also run all tests and get a coverage report with

(permissions) $ make coverage

And when ready to test everything as an installed package (bonus point if using make clean before)

(permissions) $ make tox

Thanks

  • Sebastián Ramírez, for creating FastAPI
  • William, for fixing my stupid bug
Comments
  • Allow permission check on collection of resources

    Allow permission check on collection of resources

    If I'm not mistaken permissions can only be checked on a single resource. When implementing a bulk api it would be very convenient to be able to check permissions on a list of resources all at once:

    def update_items(items: List[Item]=Permission("edit", get_items), body: ...):
        ...
    
    wontfix 
    opened by cmgreen210 4
  • Permissions on Data

    Permissions on Data

    I am new to fast API, and I searching to implement permissions on the data level. I was reading the documentation of fastapi-permissions but did find what I need. Can we Implement permissions on data level? (example, I can give user A to see an item with id 1, and give the User B access to edit item with id:2 and 4)

    Thank you.

    opened by imadmoussa1 3
  • Convert non iterable permissions to set, rather than just strings

    Convert non iterable permissions to set, rather than just strings

    On my system I want to use an enum for permissions:

    class Permissions(Enum):
        create = "CREATE"
        read = "READ"
        update = "UPDATE"
        delete = "DELETE
    

    However, the permissions doesnt get converted to a set on line 181.

    opened by mcleantom 0
  • Project maintenance and future?

    Project maintenance and future?

    This project seems to be fairly popular and I was unable to find a better alternative. However, seeing that the last commit in this project was almost two years ago, issues stay unanswered, PRs not reviewed and not merged, I'd consider this project no longer being actively maintained. For that reason I am reluctant to use it, and I'm sure others are as well.

    What can we as a community do to help maintain this project?

    opened by pmlk 0
  • Can't access pydantic principals

    Can't access pydantic principals

    I create a pydantic model following the example described in package readme:

            class SuperUser(BaseModel):    
              username: Optional[str]
    
               def principals(self):
                  return ["group:admin"]
    

    But when I call get_active_principals function, it does not append the principal prop from superuser class...I think pydantic does not allow this.

    opened by Master-Y0da 0
  • Use polymorphic principals

    Use polymorphic principals

    Implements polymorphic principals using dataclasses instead of colon-delimited strings, resolving #6. This offers the advantage of more clear semantic separation between the method and value of a principal, whilst still permitting the user to define their own principals.

    The implementation is mostly backwards-compatible, except where outlined. A version number bump will be required on the subsequent release if this PR is merged.

    Introduces a new Principal base class, from which the new default principals of UserPrincipal, RolePrincipal, and ActionPrincipal are defined. These denote the semantics of "is the user x", "has the role x", and "can do action x" respectively.

    Slightly changes the behaviour of list_permissions: instead of having a reserved magic string (permissions:*) to denote the default value in the returned dict, it instead returns a PermissionSet. The PermissionSet is a subclass of dict, which exposes a default attribute to specify what the default permission is, for the given user and resource. Any logic that previously depended on the permissions:* string will need to be rewritten to check the value of the default attribute instead, making this a (slightly) breaking change.

    Since the principals are no longer strings, the example app's show_items route (/items/) will return json objects containing the "method" and "value" fields for permissionsslightly, instead of the previous string representation. This is due to pydantic's clever handling of dataclasses. Note in particular that the permissions:* magic string will not be returned, and so additional logic would need to be implemented for anyone who depends upon this string being returned.

    Also adds some additional tests for the subtle case in which a "deny all" entry in the ACL precedes an "allow all".

    opened by eddsalkield 1
Releases(0.2.6)
  • 0.2.6(Aug 4, 2020)

    0.2.6 - OpenAPI Tests, Aug. 2020

    • Changed the list based ACLs in the example app to catch errors in the test
    • Added tests for the OpenAPI specification of the example app This is a reaction to the "List Fix" problems I had and that were finally resolved by William.
    Source code(tar.gz)
    Source code(zip)
  • 0.2.5(Jul 29, 2020)

Owner
Holger Frey
Twitter: @holgerfrey
Holger Frey
A fully tested, abstract interface to creating OAuth clients and servers.

Note: This library implements OAuth 1.0 and not OAuth 2.0. Overview python-oauth2 is a python oauth library fully compatible with python versions: 2.6

Joe Stump 3k Jan 02, 2023
Flask JWT Router is a Python library that adds authorised routes to a Flask app.

Read the docs: Flask-JWT-Router Flask JWT Router Flask JWT Router is a Python library that adds authorised routes to a Flask app. Both basic & Google'

Joe Gasewicz 52 Jan 03, 2023
Foundation Auth Proxy is an abstraction on Foundations' authentication layer and is used to authenticate requests to Atlas's REST API.

foundations-auth-proxy Setup By default the server runs on http://0.0.0.0:5558. This can be changed via the arguments. Arguments: '-H' or '--host': ho

Dessa - Open Source 2 Jul 03, 2020
python implementation of JSON Web Signatures

python-jws 🚨 This is Unmaintained 🚨 This library is unmaintained and you should probably use For histo

Brian J Brennan 57 Apr 18, 2022
Auth for use with FastAPI

FastAPI Auth Pluggable auth for use with FastAPI Supports OAuth2 Password Flow Uses JWT access and refresh tokens 100% mypy and test coverage Supports

David Montague 95 Jan 02, 2023
JWT authentication for Pyramid

JWT authentication for Pyramid This package implements an authentication policy for Pyramid that using JSON Web Tokens. This standard (RFC 7519) is of

Wichert Akkerman 73 Dec 03, 2021
Python module for generating and verifying JSON Web Tokens

python-jwt Module for generating and verifying JSON Web Tokens. Note: From version 2.0.1 the namespace has changed from jwt to python_jwt, in order to

David Halls 210 Dec 24, 2022
Django Authetication with Twitch.

Django Twitch Auth Dependencies Install requests if not installed pip install requests Installation Install using pip pip install django_twitch_auth A

Leandro Lopes Bueno 1 Jan 02, 2022
Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster

Kube OpenID Connect is an application that can be used to easily enable authentication flows via OIDC for a kubernetes cluster. Kubernetes supports OpenID Connect Tokens as a way to identify users wh

7 Nov 20, 2022
Ready to use and customizable Authentications and Authorisation management for FastAPI ⚡

AuthenticationX 💫 Ready-to-use and customizable Authentications and Oauth2 management for FastAPI ⚡ Source Code: https://github.com/yezz123/AuthX Doc

Yasser Tahiri 404 Dec 27, 2022
This script will pull and analyze syscalls in given application(s) allowing for easier security research purposes

SyscallExtractorAnalyzer This script will pull and analyze syscalls in given application(s) allowing for easier security research purposes Goals Teach

Truvis Thornton 18 Jul 09, 2022
Crie seus tokens de autenticação com o AScrypt.

AScrypt tokens O AScrypt é uma forma de gerar tokens de autenticação para sua aplicação de forma rápida e segura. Todos os tokens que foram, mesmo que

Jaedson Silva 0 Jun 24, 2022
Phishing Abusing Microsoft 365 OAuth Authorization Flow

Microsoft365_devicePhish Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack This is a simple proof-of-concept script that allows an at

bigb0ss 11 Dec 11, 2022
An introduction of Markov decision process (MDP) and two algorithms that solve MDPs (value iteration, policy iteration) along with their Python implementations.

Markov Decision Process A Markov decision process (MDP), by definition, is a sequential decision problem for a fully observable, stochastic environmen

Yu Shen 31 Dec 30, 2022
Simple yet powerful authorization / authentication client library for Python web applications.

Authomatic Authomatic is a framework agnostic library for Python web applications with a minimalistic but powerful interface which simplifies authenti

1k Dec 28, 2022
This is a Token tool that gives you many options to harm the account.

Trabis-Token-Tool This is a Token tool that gives you many options to harm the account. Utilities With this tools you can do things as : ·Delete all t

Steven 2 Feb 13, 2022
Per object permissions for Django

django-guardian django-guardian is an implementation of per object permissions [1] on top of Django's authorization backend Documentation Online docum

3.3k Jan 01, 2023
This Python based program checks your CC Stripe Auth 1$ Based Checker

CC-Checker This Python based program checks your CC Stripe Auth 1$ Based Checker About Author Coded by xBlackx Reach Me On Telegram @xBlackx_Coder jOI

xBlackxCoder 11 Nov 20, 2022
Flask App With Login

Flask App With Login by FranciscoCharles Este projeto basico é o resultado do estudos de algumas funcionalidades do micro framework Flask do Python. O

Charles 3 Nov 14, 2021
Complete Two-Factor Authentication for Django providing the easiest integration into most Django projects.

Django Two-Factor Authentication Complete Two-Factor Authentication for Django. Built on top of the one-time password framework django-otp and Django'

Bouke Haarsma 1.3k Jan 04, 2023