我一直在看这篇文章:http://pythonhosted.org/Flask-Principal/#granular-resource-protection
现在虽然它目前的工作方式没有任何问题但我看不出它是非常有用的,因为在登录时所有帖子都被读取并且EditBlogPostNeed
被添加到身份中。
想象一下,如果我写的不仅仅是正常数量的帖子,它将长期不是一个非常好的策略,因为我想查看帖子,因为我访问视图/posts/<post_id>
。
有没有办法使用Flask Principal
检查每个视图请求?
我当然可以通过懒惰的关系查询和过滤器轻松解决它,但我想使用Flask Principal
。
我不确定我完全理解你的问题,但这可能有所帮助。在Flask应用程序中,我使用Flask-Principal作为角色权限,例如admin和editor,我也使用它来进行粒度资源保护,如Flask-Principal docs中所述。在我的情况下,我正在检查用户是否有权访问特定帐户。在每个视图中加载标识并检查权限。
在视图中:
@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
自定义权限:
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)
并在身份加载器功能:
if hasattr(current_user, 'assigned_accounts'):
for account_id in current_user.assigned_accounts():
identity.provides.add(auth.ViewAccountNeed(unicode(account_id)))
虽然Flask-Principal是最受欢迎的插件,但它不必要复杂,它在我需要它的大多数情况下都不起作用。我一直试图强迫它以我喜欢的方式工作,但我从未成功过。幸运的是,我发现了一个非常简单和轻量级的模块 - permission:
首先,您需要通过继承Rule
然后覆盖check()
和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'))
然后通过继承Permission
并覆盖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()
有4种方法可以使用上面定义的UserPermission
:
1.用作视图装饰器
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.在视图代码中使用
from .permissions import UserPermission
@app.route('/settions')
def settings():
permission = UserPermission()
if not permission.check()
return permission.deny()
return render_template('settings.html')
3.在视图代码中使用(使用with
语句)
from .permissions import UserPermission
@app.route('/settions')
def settings():
with UserPermission():
return render_template('settings.html')
4.在Jinja2模板中使用
首先,您需要将已定义的权限注入模板上下文:
from . import permissions
@app.context_processor
def inject_vars():
return dict(
permissions=permissions
)
然后在模板中:
{% if permissions.UserPermission().check() %}
<a href="{{ url_for('new') }}">New</a>
{% endif %}
我在这个主题上找到的一切似乎都过于迟钝了。虽然不是我最初想要的,但我决定在我的视图功能中手动处理。它更明确,它减少了对数据库的额外查询。请注意,我仍然使用flask-security
进行开箱即用的基于角色的身份验证(它仍然通过flask-principal
通过其@roles_accepted('role')
装饰器实现。
@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)
我的回答是基于这样的假设,即您已经知道flask主体是如何工作的,以及它如何与数据库集成。
首先,我们只需将Needs存储在数据库中,如果您不知道原因,我建议您不要在下面阅读我的答案
然后,回到你的问题,我们需要编辑一篇文章,如何控制它呢?
@app.route('/article/edit/<id>'):
@Permission(Need('edit', 'article')).require()
def article(id):
pass
用户的身份
id = identity(user.id)
id.provide.add(Need('edit','article'))
然后用户有权编辑文章。即使用户不是文章的作者,@Permission(Need('edit', 'article')).require()
也会为每篇文章返回true,这是你的问题,对吧?
以下是我如何解决这个问题
因为默认的Permission.require()没有提供任何args传递,所以我定义了我自己的Permisson和IdentityContext并传入了文章id和文章Model,然后我用flask登录的current_user.id查看了文章的user_id。
class MyPermission(Permission):
pass
class MyIdentityContext():
pass
如果用户是文章的作者,那么我返回True,用户可以编辑文章,如果没有,则返回False,然后它就可以了。
--------我稍后会更新更多细节------------