【问题标题】:Flask Principal granular resource on demandFlask Principal 按需粒度资源
【发布时间】:2013-08-19 17:20:34
【问题描述】:

我一直在看这个帖子: http://pythonhosted.org/Flask-Principal/#granular-resource-protection

现在虽然它当前的工作方式没有任何问题,但我看不出它非常有用,因为在登录时所有帖子都被读取,EditBlogPostNeed 被添加到身份中。

想象一下,如果我写的帖子数量超过了正常数量,从长远来看,这将不是一个很好的策略,因为我想在访问视图 /posts/<post_id> 时查看帖子。

有没有办法使用 Flask Principal 对每个视图请求进行检查?

我当然可以通过惰性关系查询和过滤器轻松解决它,但我想使用Flask Principal

【问题讨论】:

  • 嘿,您找到解决方案了吗?
  • 不,我停止使用烧瓶,因为这个和其他一些在烧瓶中不容易解决的问题。我现在使用 webob 来实现完全控制。
  • 这里是 Flask-Principal github 上的相关问题:github.com/mattupstate/flask-principal/issues/6

标签: python flask flask-principal


【解决方案1】:

不确定我是否完全理解您的问题,但这可能会有所帮助。在 Flask 应用程序中,我将 Flask-Principal 用于角色权限,例如管理员和编辑器,我还将它用于细粒度的资源保护,如 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)))

【讨论】:

    【解决方案2】:

    虽然 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()
    

    上面定义的UserPermission有4种使用方式:

    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 %}
    

    【讨论】:

      【解决方案3】:

      我能找到的关于这个主题的一切似乎都过于迟钝。虽然不是我最初想要的,但我决定在我的视图函数中简单地手动处理它。它更加明确,并且减少了对数据库的额外查询。请注意,我仍在使用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)
      

      【讨论】:

      • 你触动了我的心
      【解决方案4】:

      我的回答是基于您已经知道烧瓶主体的工作原理以及它如何与数据库集成的假设。

      首先,我们只需要在数据库中存储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() 没有提供任何参数传入,所以我定义了自己的 Permisson 和 IdentityContext 并传入文章 id 和文章模型,然后我检查文章的 user_id 和烧瓶登录的current_user.id

      class MyPermission(Permission):
          pass
      
      
      class MyIdentityContext():
      
          pass
      

      如果用户是文章的作者,那么我返回True,用户可以编辑文章,如果不是,返回False,那么它可以工作。

      --------我稍后会更新更多细节------------

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-08-02
        • 1970-01-01
        • 1970-01-01
        • 2013-06-23
        • 2020-01-23
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多