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
Abusing Microsoft 365 OAuth Authorization Flow for Phishing Attack

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

Optiv Security 76 Jan 02, 2023
CheckList-Api - Created with django rest framework and JWT(Json Web Tokens for Authentication)

CheckList Api created with django rest framework and JWT(Json Web Tokens for Aut

shantanu nimkar 1 Jan 24, 2022
Extending the Django authentication system with a phone verification step.

Extending the Django authentication system with a phone verification step.

Miguel Grinberg 50 Dec 04, 2022
Authware API wrapper for Python 3.5+

AuthwarePy Asynchronous wrapper for Authware in Python 3.5+ View our documentation 📲 Installation Run this to install the library via pip: pip instal

Authware 3 Feb 09, 2022
A module making it easier to manage Discord oAuth with Quart

quart_discord A module making it easier to manage Discord oAuth with Quart Install pip install git+https://github.com/xelA/ 5 Oct 27, 2022

Graphical Password Authentication System.

Graphical Password Authentication System. This is used to increase the protection/security of a website. Our system is divided into further 4 layers of protection. Each layer is totally different and

Hassan Shahzad 12 Dec 16, 2022
Quick and simple security for Flask applications

Note This project is non maintained anymore. Consider the Flask-Security-Too project as an alternative. Flask-Security It quickly adds security featur

Matt Wright 1.6k Dec 19, 2022
REST implementation of Django authentication system.

djoser REST implementation of Django authentication system. djoser library provides a set of Django Rest Framework views to handle basic actions such

Sunscrapers 2.2k Jan 01, 2023
A Python inplementation for OAuth2

OAuth2-Python Discord Inplementation for OAuth2 login systems. This is a simple Python 'app' made to inplement in your programs that require (shitty)

Prifixy 0 Jan 06, 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
Corsair_scan is a security tool to test Cross-Origin Resource Sharing (CORS).

Welcome to Corsair_scan Corsair_scan is a security tool to test Cross-Origin Resource Sharing (CORS) misconfigurations. CORS is a mechanism that allow

Santander Security Research 116 Nov 09, 2022
Imia is an authentication library for Starlette and FastAPI (python 3.8+).

Imia Imia (belarussian for "a name") is an authentication library for Starlette and FastAPI (python 3.8+). Production status The library is considered

Alex Oleshkevich 91 Nov 24, 2022
A JSON Web Token authentication plugin for the Django REST Framework.

Simple JWT Abstract Simple JWT is a JSON Web Token authentication plugin for the Django REST Framework. For full documentation, visit django-rest-fram

Simple JWT 3.3k Jan 01, 2023
This script helps you log in to your LMS account and enter the currently running session

This script helps you log in to your LMS account and enter the currently running session, all in a second

Ali Ebrahimi 5 Sep 01, 2022
Minimal authorization through OO design and pure Ruby classes

Pundit Pundit provides a set of helpers which guide you in leveraging regular Ruby classes and object oriented design patterns to build a simple, robu

Varvet 7.8k Jan 02, 2023
An extension of django rest framework, providing a configurable password reset strategy

Django Rest Password Reset This python package provides a simple password reset strategy for django rest framework, where users can request password r

Anexia 363 Dec 24, 2022
Ready-to-use and customizable users management for FastAPI

FastAPI Users Ready-to-use and customizable users management for FastAPI Documentation: https://frankie567.github.io/fastapi-users/ Source Code: https

François Voron 2.4k Jan 04, 2023
Ready to use and customizable Authentications and Authorisation management for FastAPI ⚡

AuthenticationX 💫 Ready-to-use and customizable Authentications and Oauth2 management for FastAPI ⚡

Yasser Tahiri 408 Jan 05, 2023
Simple two factor authemtication system, made by me.

Simple two factor authemtication system, made by me. Honestly, i don't even know How 2FAs work I just used my knowledge and did whatever i could. Send

Refined 5 Jan 04, 2022
Generate payloads that force authentication against an attacker machine

Hashgrab Generates scf, url & lnk payloads to put onto a smb share. These force authentication to an attacker machine in order to grab hashes (for exa

xct 35 Dec 20, 2022