A flask extension for managing permissions and scopes

Overview

Flask-Pundit Build Status

A simple flask extension to organize resource authorization and scoping. This extension is heavily inspired by the ruby Pundit library.

Installation

pip install flask-pundit

Initialization

You can initialize the extension in one of 2 ways -

  1. pundit = FlaskPundit(app) where app is the application object.
  2. pundit.init_app(app) after constructing the FlaskPundit object without an app object.

When initializing the extension, you can provide an optional policies_path parameter which tells Flask-Pundit where to find your policy classes. If no value is specified this defaults to policies.

What is this policies_path exactly?

Flask-Pundit expects you to have 1 policy per model class. To find the Policy for a particular model it needs to know where to look. That is the policies_path.

Policies

A policy class defines the 'rules' used to authorize a model. You can write your own policy class as follows:

class PostPolicy():
        def __init__(self, user, post):
                self.user = user
                self.post = post
        
        def get(self):
                return self.user == 'admin' and self.post.id == 1

The user object is the currently 'logged' in user and the post object is the model instance you want to authorize. The get method is an authorization 'action' handler that you might want to execute when a user is trying to read a post.

You could alternatively define your own BasePolicy class and extend it in a similar fashion or use the ApplicationPolicy class provided by the extension in which case the code would be:

from flask_pundit.application_policy import ApplicationPolicy

class PostPolicy(ApplicationPolicy):
        def get(self):
                return self.user == 'admin' and self.record.id == 1

Note that now we're using record inside the method. By inheriting from ApplicationPolicy all instance methods now use record to represent the model instance being authorized.

To authorize a post object inside a resource (or a blueprint or just a app.route decorated function) you would call self.pundit.authorize(post). This will cause flask-pundit to look for the PostPolicy class at policies/post. If you want a different root to be searched, you can specify the policies_path when initializing the extension.

This example shows how to use the authorize method in a single module app.

app = Flask('blog_series')
pundit = FlaskPundit(app)

@app.route('/blogs/<id>')
def read_blog_post(id):
        blog = Post.get_by_id(id)
        if pundit.authorize(post):
                return blog
        return ForbiddenError, 403

The authorize method takes 3 parameters:

  1. A record - This can be either an object or class and corresponds to a 'model' that you're doing the authorization on.

  2. An action - This corresponds to the policy method that you want to invoke for doing the authorization. If no value is provided it defaults to request.method.lowercase(). Thus in the previous snippet the get method of a BlogPolicy object would be invoked.

  3. A user - This is akin to the currently 'logged in' user. If no user object is provided, flask-pundit tries to pick either flask.g.user or flask.g.current_user, whichever is available.

Thus in the above set of examples, invoking authorize executes the get method in the PostPolicy class at policies/post with the record being the post object filtered by id.

Scopes

The authorize method acts more as a true/false guard. On the other hand the policy_scope method returns a 'scoped' version of a model. For example, if you have a page with all posts, you might want to let an admin see all of them but restrict the ones staff users see. This is where you'd want to use policy_scope instead of authorize.

To do so, you need to define a scope method in your policy.

from flask_pundit.application_policy import ApplicationPolicy

class PostPolicy(ApplicationPolicy):
        def get(self):
                return self.user == 'admin' and self.record.id == 1


        def scope(self):
                if self.user == 'admin':
                        return record.all()
                return record.filter_by(author='staff')
        

When you call the policy_scope(model) with a model class (it doesn't make sense to pass an object here), the scope method gets called.

from app import pundit

@app.route('/posts)
def index():
        all_posts = pundit.policy_scope(Post)
        return all_posts

The examples here show how to return all posts for an admin and only staff posts for a staff user.

The policy_scope method takes 2 arguments:

  1. A model - This is the class that is to be 'scoped'.

  2. A user object - This is just like the user object in the authorize case.

Verification

Flask-Pundit has 2 decorators you can use to verify authorize/ policy_scope has been called. They are verify_authorized and verify_policy_scoped.

In a single module app you would use verify_authorized as:

from flask_pundit import verify_authorized
from app import app, pundit

@app.route('/posts/<id>')
@verify_authorized
def read_blog_post(id):
        blog_post = Post.get_by_id(id)
        if pundit.authorize(blog_post):
                return blog_post
        return ForbiddenError, 403

If you remove the call to authorize the decorator will throw a RuntimeError as it expects a call but found none.

The verify_policy_scoped decorator would be used in the exact same way. Using these 2 would prove more useful if you're using something like Flask-Restful where you could specify these as method_decorators in your resource, if you wanted all the methods to be verified.

If you prefer not using decorators you could use pundit._verify_authorized and pundit._verify_policy_scoped directly inside your methods. Calling them directly will return True or False.

Custom Policy class

You could override the policy class lookup behaviour by adding a __policy_class__ property on your models. This should reference the class that you want to be used against this model. For example,

from policies.commenting import CommentingPolicy

class Comment:
        __policy_class__ = CommentingPolicy

Now when doing either authorize or policy_scope against an instance of Comment or the class itself, CommentingPolicy will be used.

License

Licensed under MIT license

Comments
  • Feature/add authorized or scoped decorator

    Feature/add authorized or scoped decorator

    This is just a convenience decorator for me. I’m blanket enforcing that resources do some sort of authorization. I don’t really care if they’re using a scope or direct auth, as long as they’re checking one of the two.

    For the tests I just copied the existing tests you have for those decorators and made sure that it will pass if either function is called.

    I’m happy to add a mention of this decorator into the readme if you agree it’s a good idea.

    If I end up writing anything else while using this, is just creating a pull request a good way to offer back the suggestions? Or would you rather I put in an issue first and ask?

    Thanks!

    opened by dbanty 3
  • Support Flask 1.0.0+

    Support Flask 1.0.0+

    Hello,

    I was just about to try using Flask-pundit which looks amazing and exactly what I've been searching for. However, I'm using Flask 1.0.2 which pip thinks is incompatible with this extension. I see you already changed the requirements to the new version of Flask, will you be uploading a release soon with this support?

    Thanks!

    opened by dbanty 3
  • Unpin Flask requirement

    Unpin Flask requirement

    As Flask releases bug fixes (e.g. 1.0.3), new features (e.g. 1.1.0), etc. that do not break compatibility (according to semver) it would be nice for Pundit to not complain about using newer releases. I've updated setup.py to reflect this.

    opened by dbanty 2
  • Python 3 Support

    Python 3 Support

    Using Python 3.7 I get an IndexError when passing in a resource to the authorize function. It looks like the regex pattern in Python 3 is matching a final, empty string, so when name[-1] is called, it's an error.

    The basic fix is pretty easy, just check if the string is empty in the internal dasherize function and if so, return that empty string.

    I'm fixing it anyway for my own usage, so I'll put in a pull request.

    opened by dbanty 0
  • Remove scope classes and replace them with scope methods

    Remove scope classes and replace them with scope methods

    Scope classes are just unnecessary complexity and can be achieved by a method.

    I still like the idea of having a policy_scopemethod vs just calling authorize with an action as scope. This is primarily because its more indicative that this is 'different' behaviour.

    opened by anurag90x 0
  • Support Flask 2

    Support Flask 2

    Right now Flask Pundit has a dependency on flask. flask<2,>=1.0.2

    https://flask.palletsprojects.com/en/2.0.x/changes/

    The major changes are the drop of official support for Py2 but since this lib already supports py3.7, It might be just fine to bump the dependency

    opened by sergioisidoro 0
Releases(1.0.2)
Owner
Anurag Chaudhury
Anurag Chaudhury
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

null 3.3k Jan 1, 2023
Simple extension that provides Basic, Digest and Token HTTP authentication for Flask routes

Flask-HTTPAuth Simple extension that provides Basic and Digest HTTP authentication for Flask routes. Installation The easiest way to install this is t

Miguel Grinberg 1.1k Jan 5, 2023
Simple extension that provides Basic, Digest and Token HTTP authentication for Flask routes

Flask-HTTPAuth Simple extension that provides Basic and Digest HTTP authentication for Flask routes. Installation The easiest way to install this is t

Miguel Grinberg 940 Feb 13, 2021
An open source Flask extension that provides JWT support (with batteries included)!

Flask-JWT-Extended Features Flask-JWT-Extended not only adds support for using JSON Web Tokens (JWT) to Flask for protecting views, but also many help

Landon Gilbert-Bland 1.4k Jan 4, 2023
Simple Login - Login Extension for Flask - maintainer @cuducos

Login Extension for Flask The simplest way to add login to flask! Top Contributors Add yourself, send a PR! How it works First install it from PyPI. p

Flask Extensions 181 Jan 1, 2023
Simple Login - Login Extension for Flask - maintainer @cuducos

Login Extension for Flask The simplest way to add login to flask! Top Contributors Add yourself, send a PR! How it works First install it from PyPI. p

Flask Extensions 132 Feb 10, 2021
Simple Login - Login Extension for Flask - maintainer @cuducos

Login Extension for Flask The simplest way to add login to flask! How it works First, install it from PyPI: $ pip install flask_simplelogin Then, use

Flask Extensions 181 Jan 1, 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 3, 2023
User Authentication in Flask using Flask-Login

User-Authentication-in-Flask Set up & Installation. 1 .Clone/Fork the git repo and create an environment Windows git clone https://github.com/Dev-Elie

ONDIEK ELIJAH OCHIENG 31 Dec 11, 2022
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
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
Doing the OAuth dance with style using Flask, requests, and oauthlib.

Flask-Dance Doing the OAuth dance with style using Flask, requests, and oauthlib. Currently, only OAuth consumers are supported, but this project coul

David Baumgold 915 Dec 28, 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
Doing the OAuth dance with style using Flask, requests, and oauthlib.

Flask-Dance Doing the OAuth dance with style using Flask, requests, and oauthlib. Currently, only OAuth consumers are supported, but this project coul

David Baumgold 799 Feb 17, 2021
Doing the OAuth dance with style using Flask, requests, and oauthlib.

Flask-Dance Doing the OAuth dance with style using Flask, requests, and oauthlib. Currently, only OAuth consumers are supported, but this project coul

David Baumgold 802 Feb 22, 2021
Flask Implementation of a login page and some basic functionality.

login_page Flask Implementation of a login page and some basic functionality. How to Run $ chmod +x run.sh setup.sh $ # run setup.sh only if the datab

null 3 Jun 3, 2021