Flask Principal granular resource on demand
Asked Answered
L

4

7

I've been looking at this post: http://pythonhosted.org/Flask-Principal/#granular-resource-protection

Now while there is nothing wrong with how it is currently working I can't see that it is very usable since at the time of login all posts are read and the EditBlogPostNeed is added to the identity.

Imagine if I have been writing more than the normal number of posts it will long term not be a very good strategy as I would like to check the post as I access the view /posts/<post_id>.

Is there a way to do this check on each request for the view using Flask Principal?

I can of course get around it very easily with a lazy relationship query and filter but I want to use Flask Principal.

Lariat answered 19/8, 2013 at 17:20 Comment(2)
Nope and I stopped using flask because of this and some other issues not easily solvable in flask. I'm now using webob instead for full control.Lariat
Here is the related issue on Flask-Principal github: github.com/mattupstate/flask-principal/issues/6Anacoluthon
T
1

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_required
def 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)))
Tiemroth answered 15/7, 2014 at 2:15 Comment(0)
A
1

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 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')

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 %}
Anacoluthon answered 18/12, 2015 at 13:42 Comment(0)
L
1

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)
Loden answered 13/9, 2018 at 22:53 Comment(1)
u touched my heartAmplexicaul
G
0

My answer is based on the assumption that you already know how flask principal works, and how it's integrated with database.

First, We only need to store Needs in the database, if you don't know why, I don't recommend you to read my answer below

Then, back to your question,We need to edit an article,how to grannel control it?

@app.route('/article/edit/<id>'):
@Permission(Need('edit', 'article')).require()
def article(id):
    pass

User's identity

id = identity(user.id)
id.provide.add(Need('edit','article'))

Then the user have the permission to edit the article.@Permission(Need('edit', 'article')).require() will return true for every article even though the user is not the author of the article,This is your problem ,right??

Below is how I solve this problem

because the default Permission.require() doesn't provide any args to pass into , So I define my own the Permisson and IdentityContext and pass in the article id and the article Model, then I check the article's user_id with flask login's current_user.id

class MyPermission(Permission):
    pass


class MyIdentityContext():

    pass

If the user is the article's author then I return True, the User can edit the article, If not, return False, then it works.

--------I will update more detail later------------

Grecism answered 24/3, 2016 at 11:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.