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
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 9, 2022
Strong, Simple, and Precise security for Flask APIs (using jwt)

flask-praetorian Strong, Simple, and Precise security for Flask APIs API security should be strong, simple, and precise like a Roman Legionary. This p

Tucker Beck 321 Dec 18, 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
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 Feb 17, 2021
Strong, Simple, and Precise security for Flask APIs (using jwt)

flask-praetorian Strong, Simple, and Precise security for Flask APIs API security should be strong, simple, and precise like a Roman Legionary. This p

Tucker Beck 266 Feb 15, 2021
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 9, 2022
Luca Security Concept

Luca Security Concept This is the document source of luca's security concept. Please go here for the HTML version: https://luca-app.de/securityconcept

luca 43 Oct 22, 2022
Python's simple login system concept - Advanced level

Simple login system with Python - For beginners Creating a simple login system using python for beginners this repository aims to provide a simple ove

Low_Scarlet 1 Dec 13, 2021
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 4, 2023
FastAPI-Login tries to provide similar functionality as Flask-Login does.

FastAPI-Login FastAPI-Login tries to provide similar functionality as Flask-Login does. Installation $ pip install fastapi-login Usage To begin we hav

null 417 Jan 7, 2023
FastAPI extension that provides JWT Auth support (secure, easy to use, and lightweight)

FastAPI JWT Auth Documentation: https://indominusbyte.github.io/fastapi-jwt-auth Source Code: https://github.com/IndominusByte/fastapi-jwt-auth Featur

Nyoman Pradipta Dewantara 468 Jan 1, 2023
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 2, 2023
Implements authentication and authorization as FastAPI dependencies

FastAPI Security Implements authentication and authorization as dependencies in FastAPI. Features Authentication via JWT-based OAuth 2 access tokens a

Jacob Magnusson 111 Jan 7, 2023
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
Easy and secure implementation of Azure AD for your FastAPI APIs 🔒 Single- and multi-tenant support.

Easy and secure implementation of Azure AD for your FastAPI APIs ?? Single- and multi-tenant support.

Intility 220 Jan 5, 2023
A full Rest-API With Oauth2 and JWT for request & response a JSON file Using FastAPI and SQLAlchemy 🔑

Pexon-Rest-API A full Rest-API for request & response a JSON file, Building a Simple WorkFlow that help you to Request a JSON File Format and Handling

Yasser Tahiri 15 Jul 22, 2022
Simple implementation of authentication in projects using FastAPI

Fast Auth Facilita implementação de um sistema de autenticação básico e uso de uma sessão de banco de dados em projetos com tFastAPi. Instalação e con

null 3 Jan 8, 2022
Auth-Starters - Different APIs using Django & Flask & FastAPI to see Authentication Service how its work

Auth-Starters Different APIs using Django & Flask & FastAPI to see Authentication Service how its work, and how to use it. This Repository based on my

Yasser Tahiri 7 Apr 22, 2022
Authentication with fastapi and jwt cd realistic

Authentication with fastapi and jwt cd realistic Dependencies bcrypt==3.1.7 data

Fredh Macau 1 Jan 4, 2022