• Stars
    star
    102
  • Rank 335,584 (Top 7 %)
  • Language
    Python
  • License
    MIT License
  • Created about 10 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Simple and flexible permission control for Flask app.

Permission

Latest Version The MIT License

Simple and flexible permission control for Flask app.

Features

  • Simple: all you need to do is subclassing Rule and Permission class.
  • Flexible: support rule inheritance and bitwise operations(& and |) to build your own rules.

Installation

$ pip install permission

Rule

Rule has 3 methods which can be overrided:

  • base(): define base rule.
  • check(): determine whether this rule should be passed or not.
  • deny(): will be executed when check() failed.

You should always override check() and deny() while overriding base() as needed.

Permission

Permission has 1 method which can be overrided:

  • rule(): define rule needed by this permission

You should always override rule().

Permission has 2 instance methods you can use in codes:

  • check(): call this to check rule of this permission
  • deny(): call this to execute codes when check() failed

Usage

First you need to define your own rules by subclassing Rule then override check() and deny():

# rules.py
from flask import session, flash, redirect, url_for
from permission import Rule


class UserRule(Rule):
    def check(self):
        """Check if there is a user signed in."""
        return 'user_id' in session

    def deny(self):
        """When no user signed in, redirect to signin page."""
        flash('Sign in first.')
        return redirect(url_for('signin'))

Then you define permissions by subclassing Permission and override rule():

# permissions.py
from permission import Permission
from .rules import UserRule


class UserPermission(Permission):
    """Only signin user has this permission."""
    def rule(self):
        return UserRule()

There are 4 ways to use the UserPermission defined above:

1. Use as view decorator

from .permissions import UserPermission


@app.route('/settings')
@UserPermission()
def settings():
    """User settings page, only accessable for sign-in user."""
    return render_template('settings.html')

2. Use in view codes

from .permissions import UserPermission


@app.route('/settions')
def settings():
    permission = UserPermission()
    if not permission.check()
        return permission.deny()
    return render_template('settings.html')

3. Use in view codes (using with statement)

from .permissions import UserPermission


@app.route('/settions')
def settings():
    with UserPermission():
        return render_template('settings.html')

Note: if you don't raise an exception when the permission check failed (in other words, a rule's deny() will be called), PermissionDeniedException will be raised in order to stop the execution of the with-body codes. By the way, you can import this exception as needed:

from permission import PermissionDeniedException

4. Use in Jinja2 templates

First you need to inject your defined permissions to template context:

from . import permissions


@app.context_processor
def inject_vars():
    return dict(
        permissions=permissions
    )

then in templates:

{% if permissions.UserPermission().check() %}
    <a href="{{ url_for('new') }}">New</a>
{% endif %}

Rule Inheritance

Need to say, inheritance here is not the same thing as Python class inheritance, it's just means you can use RuleA as the base rule of RuleB.

We achieve this by overriding base().

Let's say an administrator user should always be a user:

# rules.py
from flask import session, abort, flash, redirect, url_for
from permission import Rule


class UserRule(Rule):
    def check(self):
        return 'user_id' in session

    def deny(self):
        flash('Sign in first.')
        return redirect(url_for('signin'))


class AdminRule(Rule):
    def base(self):
        return UserRule()

    def check(self):
        user_id = int(session['user_id'])
        user = User.query.filter(User.id == user_id).first()
        return user and user.is_admin

    def deny(self):
        abort(403)

Rule Bitwise Operations

  • RuleA & RuleB means it will be passed when both RuleA and RuleB are passed.
  • RuleA | RuleB means it will be passed either RuleA or RuleB is passed.

Let's say we need to build a forum with Flask. Only the topic creator and administrator user can edit a topic:

First define rules:

# rules.py
from flask import session, abort, flash, redirect, url_for
from permission import Rule
from .models import User, Topic


class UserRule(Rule):
    def check(self):
        """Check if there is a user signed in."""
        return 'user_id' in session

    def deny(self):
        """When no user signed in, redirect to signin page."""
        flash('Sign in first.')
        return redirect(url_for('signin'))


class AdminRule(Rule):
    def base(self):
        return UserRule()

    def check(self):
        user_id = int(session['user_id'])
        user = User.query.filter(User.id == user_id).first()
        return user and user.is_admin

    def deny(self):
        abort(403)


class TopicCreatorRule(Rule):
    def __init__(self, topic):
        self.topic = topic
        super(TopicCreatorRule, self).__init__()

    def base(self):
        return UserRule()

    def check(self):
        return topic.user_id == session['user_id']

    def deny(self):
        abort(403)

then define permissions:

# permissions.py
from permission import Permission


class TopicAdminPermission(Permission):
    def __init__(self, topic):
        self.topic = topic
        super(TopicAdminPermission, self).__init__()

    def rule(self):
        return AdminRule() | TopicCreatorRule(self.topic)

so we can use TopicAdminPermission in edit_topic view:

from .permissions import TopicAdminPermission


@app.route('topic/<int:topic_id>/edit')
def edit_topic(topic_id):
    topic = Topic.query.get_or_404(topic_id)
    permission = TopicAdminPermission(topic)
    if not permission.check():
        return permission.deny()
    ...

License

MIT

More Repositories

1

Flask-Boost

Flask application generator for boosting your development.
Python
509
star
2

blogbar

Blogbar,聚合个人博客。
Python
144
star
3

1jingdian

[OFFLINE] 每天分享好句子。
Python
87
star
4

jquery-s2t

A jQuery plugin to convert between Simplified Chinese and Traditional Chinese.
JavaScript
71
star
5

react-redux-example

Production ready example of react & redux.
JavaScript
25
star
6

resume

Resume = Jade + YAML.
CSS
18
star
7

yprogrammer

[OFFLINE] High quality resources, for web programmers.
CSS
10
star
8

optico

Website for OPTICO Communication.
HTML
7
star
9

farbox-template-wiki

Farbox template for personal wiki.
JavaScript
6
star
10

react-wechat

Compose post of WeChat Official Account via React.
JavaScript
5
star
11

generator-rr

React+Redux scaffolding generator based on Yeoman.
JavaScript
4
star
12

transy

(暂停维护)A web app helps people translate English to Chinese.
CSS
4
star
13

airdna

品读文献,赞美科学。
CSS
4
star
14

zhongguan

中关村字典
Python
4
star
15

lishengchun

[OFFLINE] 李胜春的国画作品。
JavaScript
3
star
16

beginner

Learning resources for beginners.
3
star
17

ios-cookbook

iOS Cookbook.
2
star
18

InterfaceBuilderX

UIView codes generator for Swift App.
Vue
2
star
19

jenkins-presentation

Presentation about Jenkins.
JavaScript
2
star
20

shufa.io

Chinese Famous Calligraphic.
Python
1
star
21

iOS-Base

Instructions for iOS project.
Objective-C
1
star
22

flask-tips

Tips for Flask Web Development.
1
star
23

Kael

Build UI with the power of Function Builder.
Swift
1
star
24

Observable-Swift-Example

Example for Observable-Swift.
Swift
1
star