Flask Principal granular resource on demand Flask Principal granular resource on demand flask flask

Flask Principal granular resource on demand


Not sure I understand your question entirely, but this might help. In a Flask app, I'm using Flask-Principal for role permission such as admin and editor, and I'm also using it for granular resource protection as described in the Flask-Principal docs. In my case I'm checking if a user has permission to access a particular account. In each view the identity is loaded and the permission is checked.

In the view:

@login_requireddef call_list(id, page=1):    dept = models.Department.query.get_or_404(id)    view_permission = auth.ViewAccountPermission(dept.office.account_id)    if view_permission.can():        # do something

The custom permission:

ViewAccount = namedtuple('View', ['method', 'value'])ViewAccountNeed = partial(ViewAccount, 'view')class ViewAccountPermission(Permission):    def __init__(self, account_id):        need = ViewAccountNeed(unicode(account_id))        super(ViewAccountPermission, self).__init__(need)

And in the identity loader function:

if hasattr(current_user, 'assigned_accounts'):    for account_id in current_user.assigned_accounts():        identity.provides.add(auth.ViewAccountNeed(unicode(account_id)))


While Flask-Principal is the most popular plugin, it is unnecessary complicated and it just doesn't work in the most cases I need it. I have been trying to force it to work the way I like it, but I have never succeeded. Luckily, I have found an extremely straightforward and lightweight module - permission:

Usage

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

# rules.pyfrom flask import session, flash, redirect, url_forfrom permission import Ruleclass 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.pyfrom permission import Permissionfrom .rules import UserRuleclass 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')

4. Use in Jinja2 templates

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

from . import permissions@app.context_processordef inject_vars():    return dict(        permissions=permissions    )

then in templates:

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


Everything I can find on this topic seems overly obtuse. While not what I initially desired, I've decided to simply handle this manually in my view functions. It's more explicit, and it reduces extra queries against the database. Do note that I'm still using flask-security for its out-of-the-box role-based authentication (which is still implemented via flask-principal via its @roles_accepted('role') decorator.

@app.route('/my_accounts/', methods = ['GET'])@app.route('/my_accounts/<int:id>/', methods = ['GET'])@roles_accepted('client')def my_accounts(id=None):    if id:        account = Account.query.get_or_404(id)        if account.owner == current_user:            return render_template("my_account.html",                                   title = "Account: {0}".format(account.name),                                   account = account)        else:            abort(403)    accounts = Account.query.filter_by(owner=current_user).all()    return render_template("my_accounts.html",                           title = 'My Accounts',                           accounts = accounts)