Skip to main content

Flask plugin for content authorization and access control

Project description

Build status Code coverage Maintenance yes GitHub license Documentation Status

Flask-Authorize

Flask-Authorize is a Flask extension designed to simplify the process of incorporating Access Control Lists (ACLs) and Role-Based Access Control (RBAC) into applications housing sensitive data, allowing developers to focus on the actual code for their application instead of logic for enforcing permissions. It uses a unix-like permissions scheme for enforcing access permissions on existing content, and also provides mechanisms for globally enforcing permissions throughout an application.

Installation

To install the latest stable release via pip, run:

$ pip install Flask-Authorize

Alternatively with easy_install, run:

$ easy_install Flask-Authorize

To install the bleeding-edge version of the project (not recommended):

$ git clone http://github.com/bprinty/Flask-Authorize.git
$ cd Flask-Authorize
$ python setup.py install

Usage

Below details a minimal example showcasing how to use the extension. First, to set up the flask application with extensions:

from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from flask_authorize import Authorize

app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
login = LoginManager(app)
authorize = Authorize(app)

Defining database models:

from flask_authorize import RestrictionsMixin, AllowancesMixin
from flask_authorize import PermissionsMixin


# mapping tables
UserGroup = db.Table(
    'user_group', db.Model.metadata,
    db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
    db.Column('group_id', db.Integer, db.ForeignKey('groups.id'))
)


UserRole = db.Table(
    'user_role', db.Model.metadata,
    db.Column('user_id', db.Integer, db.ForeignKey('users.id')),
    db.Column('role_id', db.Integer, db.ForeignKey('roles.id'))
)


# models
class User(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False, unique=True)

    # `roles` and `groups` are reserved words that *must* be defined
    # on the `User` model to use group- or role-based authorization.
    roles = db.relationship('Role', secondary=UserRole)
    groups = db.relationship('Group', secondary=UserGroup)


class Group(db.Model, RestrictionsMixin):
    __tablename__ = 'groups'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False, unique=True)


class Role(db.Model, AllowancesMixin):
    __tablename__ = 'roles'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), nullable=False, unique=True)


class Article(db.Model, PermissionsMixin):
    __tablename__ = 'articles'
    __permissions__ = dict(
        owner=['read', 'update', 'delete', 'revoke'],
        group=['read', 'update'],
        other=['read']
    )

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(255), index=True, nullable=False)

Defining endpoint actions:

from flask import jsonify
from werkzeug import NotFound, Unauthorized

@app.route('/articles', methods=['POST'])
@login.logged_in
@authorize.create(Article)
def article():
    article = Article(**request.json)
    db.session.add(article)
    db.session.commit()
    return jsonify(msg='Created Article'), 200

@app.route('/articles/<int:ident>', methods=['GET', 'PUT', 'DELETE'])
@login.logged_in
def single_article(ident):
    article = db.session.query(Article).filter_by(id=ident).first()
    if not article:
        raise NotFound

    if request.method == 'GET':

        # check if the current user is authorized to read the article
        if not authorize.read(article):
            raise Unauthorized

        return jsonify(id=article.id, name=article.name), 200

    elif request.method == 'PUT':

        # check if the current user is authorized to update to the article
        if not authorize.update(article):
            raise Unauthorized

        for key, value in request.json.items():
            setattr(article, key, value)
        db.session.commit()

        return jsonify(id=article.id, name=article.name), 200

    elif request.method == 'DELETE':

        # check if the current user is associated with the 'admin' role
        if not authorize.delete(article) or \
           not authorize.has_role('admin'):
            raise Unauthorized

        db.session.delete(article)
        db.session.commit()

    return

@app.route('/articles/<int:ident>/revoke', methods=['POST'])
@login.logged_in
def revoke_article(ident):
    article = db.session.query(Article).filter_by(id=ident).first()
    if not article:
        raise NotFound

    # check if the current user can revoke the article
    if not authorize.revoke(article):
        raise Unauthorized

    article.revoked = True
    db.session.commit()

    return

Additionally, if you’ve configured your application to dispatch request processing to API functions, you can use the authorize extension object as a decorator:

@authorize.create(Article)
def create_article(name):
    article = Article(**request.json)
    db.session.add(article)
    db.session.commit()
    return article

@authorize.read
def read_article(article):
    return article

@authorize.update
def update_article(article, **kwargs):
    for key, value in request.json.items():
        setattr(article, key, value)
    db.session.commit()
    return article

@authorize.delete
def delete_article(article):
    db.session.delete(article)
    return

@authorize.revoke
def revoke_article(article):
    article.revoke = True
    db.session.commit()
    return

@authorize.has_role('admin')
def get_admin_articles():
    pass

Using the extension as a decorator goes a long way in removing boilerplate associated with permissions checking. Additionally, using the authorize extension object as a decorator will implicitly check the current user’s access to each argument or keyword argument to the function. For example, if your method takes two Article objects and merges them into one, you can add permissions for both operations like so:

@authorize.read
@authorize.create(Article)
def merge_articles(article1, article2):
    new_article = Article(name=article1.name + article.2.name)
    db.session.add(new_article)
    db.session.delete(article1, article2)
    db.session.commit()
    return new_article

This function will ensure that the current user has read access to both articles and also create permissions on the Article model itself. If the authorization criteria aren’t satisfied, an Unauthorized error will be thrown.

Finally, the authorize operator is also available in Jinja templates:

<!-- button for creating new article -->
{% if authorize.create('articles') %}
    <button>Create Article</button>
{% endif %}

<!-- display article feed -->
{% for article in articles %}

    <!-- show article if user has read access -->
    {% if authorize.read(article) %}
        <h1>{{ article.name }}</h1>

        <!-- add edit button for users who can update-->
        {% if authorize.update(article) %}
            <button>Update Article</button>
        {% endif %}

        <!-- add delete button for administrators -->
        {% if authorize.in_group('admins') %}
            <button>Delete Article</button>
        {% endif %}

    {% endif %}
{% endfor %}

Documentation

For more detailed documentation, see the Docs.

Questions/Feedback

File an issue in the GitHub issue tracker.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

Flask-Authorize-0.2.7.tar.gz (281.7 kB view details)

Uploaded Source

Built Distribution

Flask_Authorize-0.2.7-py2.py3-none-any.whl (12.8 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file Flask-Authorize-0.2.7.tar.gz.

File metadata

  • Download URL: Flask-Authorize-0.2.7.tar.gz
  • Upload date:
  • Size: 281.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.11.5

File hashes

Hashes for Flask-Authorize-0.2.7.tar.gz
Algorithm Hash digest
SHA256 b53fb2d7f78b8a9727937302de0f1917cbceab1978a4c20f1b556d76c6671dd5
MD5 1d159d543be887598b2a51115375d421
BLAKE2b-256 61a0d88b645a837c0cb6d79765dce791e7ea3da5d6c7d584cbce609c37c9fa45

See more details on using hashes here.

File details

Details for the file Flask_Authorize-0.2.7-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for Flask_Authorize-0.2.7-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 f48f2274b7c77cac341e940ea5d93b5cac2512fa685a288adb6001cf811efb29
MD5 11eca1759a17f2ce6383a04bd6599a45
BLAKE2b-256 0f22f36f1f61dd28f04188e93cc67aa83817374a6bbfcb7a559824d9a80ade46

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page