V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
hustlzp
V2EX  ›  Python

分享在 Flask 中进行简单的权限管理的代码

  •  3
     
  •   hustlzp · 2014-09-09 20:09:04 +08:00 · 12766 次点击
    这是一个创建于 3932 天前的主题,其中的信息可能已经有所发展或是发生改变。
    在项目开发中经常会遇到权限控制的问题,用过Flask-Principal,不过感觉用得非常不习惯,所以花时间倒腾了一下。

    库文件是flask_permission.py。

    然后在文件rules.py中定义细粒度的Rule。每个Rule类的一般需要定义:

    * base():定义该rule的先验rule
    * check():定义该rule的检测逻辑
    * deny():若该rule未通过,需要做出何种反馈(比如跳转登陆页、返回403等)

    接着在permissions.py中根据之前定义的rules拼装各种业务需要的权限,使用`&`与`|`操作符。

    最后就可以用了,在examples.py中提供了三种使用示例:

    * 用于view的装饰器
    * 用于view代码中
    * 用于Jinja2中

    目前用于现有的小项目还是够用的。如果权限管理的需求非常复杂的话,是hold不住的,估计得祭出RBAC...

    # 用于view的装饰器
    from ..utils.permissions import VisitorPermission
    @bp.route('/signin', methods=['GET', 'POST'])
    @VisitorPermission()
    def signin():
    """登陆"""
    form = SigninForm()
    if form.validate_on_submit():
    signin_user(form.user)
    return redirect(url_for('site.index'))
    return render_template('account/signin.html', form=form)
    # 用于view代码中
    from ..utils.permissions import QuestionAdminPermission
    @bp.route('/question/<int:uid>/delete')
    def delete_question(uid):
    """删除问题"""
    question = Question.query.get_or_404(uid)
    permission = QuestionAdminPermission(uid)
    if not permission.check():
    return permission.deny()
    db.session.delete(question)
    db.session.commit()
    return redirect(url_for('site.index'))
    # 用于Jinja2中
    def register_jinja(app):
    """注册Jinja2全局变量"""
    from .utils import permissions
    # inject vars into template context
    @app.context_processor
    def inject_vars():
    return dict(
    g_permissions=permissions,
    )
    """
    {% if g_permissions.QuestionAdminPermission(question.id).check() %}
    <a class="btn btn-default confirm" data-confirm="确认删除此问题?"
    href="{{ url_for('qa.delete_question', uid=question.id) }}">
    <span class="fa fa-trash-o"></span> 删除
    </a>
    {% endif %}
    """
    view raw examples.py hosted with ❤ by GitHub
    # coding: utf-8
    from functools import wraps
    class Permission(object):
    def __call__(self, func):
    """提供view装饰器能力"""
    @wraps(func)
    def decorator(*args, **kwargs):
    if not self.check():
    return self.deny()
    return func(*args, **kwargs)
    return decorator
    def check(self):
    """运行规则"""
    result, self.deny = self.rule.run()
    return result
    def show(self):
    """显示self.rule的规则结构,调试用"""
    self.rule.show()
    class Rule(object):
    def __init__(self):
    self.rules_list = [[(self.check, self.deny)]]
    # 若子类实现了base方法,则将其返回的rule实例的rules_list串联到self.rules_list上游
    base_rule = self.base()
    if base_rule:
    self.rules_list = Rule._and(base_rule.rules_list, self.rules_list)
    def __and__(self, other):
    """逻辑与操作(&)
    将other.rules_list串联到self.rules_list的下游,
    并返回当前实例。"""
    self.rules_list = Rule._and(self.rules_list, other.rules_list)
    return self
    def __or__(self, other):
    """逻辑或操作(|)
    将self.rules_list与other.rules_list并联起来,
    并返回当前实例"""
    for rule in other.rules_list:
    self.rules_list.append(rule)
    return self
    def show(self):
    """显示rules_list的结构"""
    for rule in self.rules_list:
    result = ", ".join([check.__repr__() for check, deny in rule])
    print(result)
    def base(self):
    """提供rule规则继承能力(串联)"""
    return None
    def run(self):
    """运行rules_list。
    若某一通道check通过,则返回成功状态
    若所有通道都无法check通过,则返回失败状态(包含最后运行失败的rule的deny方法)"""
    failed_result = None
    for rule in self.rules_list:
    for check, deny in rule:
    if not check():
    failed_result = (False, deny)
    break
    else:
    return (True, None)
    return failed_result
    def check(self):
    """当前rule的测试方法,强制子类overload"""
    raise NotImplementedError()
    def deny(self):
    """当前rule测试失败后需要执行的方法,强制子类overload"""
    raise NotImplementedError()
    @staticmethod
    def _and(rules_list_pre, rules_list_pro):
    """将rule_list_pre串联到rule_list_pro上游"""
    return [rule_pre + rule_pro
    for rule_pre in rules_list_pre
    for rule_pro in rules_list_pro]
    # coding: utf-8
    from .flask_permission import Permission
    from .rules import VisitorRule, UserRule, AdminRule, QuestionOwnerRule
    class VisitorPermission(Permission):
    """游客许可"""
    def __init__(self):
    self.rule = VisitorRule()
    class UserPermission(Permission):
    """注册用户许可"""
    def __init__(self):
    self.rule = UserRule()
    class AdminPermission(Permission):
    """管理员许可"""
    def __init__(self):
    self.rule = AdminRule()
    class QuestionAdminPermission(Permission):
    """Question管理许可
    Question拥有者/管理员均有该许可
    """
    def __init__(self, question_id):
    self.rule = AdminRule() | QuestionOwnerRule(question_id)
    view raw permission.py hosted with ❤ by GitHub
    # coding: utf-8
    from flask import redirect, url_for, abort, flash, g
    from ..models import Question
    from .flask_permission import Rule
    class VisitorRule(Rule):
    """游客"""
    def check(self):
    return not g.user
    def deny(self):
    return redirect(url_for('site.index'))
    class UserRule(Rule):
    """注册用户"""
    def check(self):
    return g.user
    def deny(self):
    flash('此操作需要登录账户')
    return redirect(url_for('account.signin'))
    class AdminRule(Rule):
    """管理员"""
    def base(self):
    return UserRule()
    def check(self):
    return g.user.is_admin
    def deny(self):
    abort(403)
    class QuestionOwnerRule(Rule):
    """问题拥有者"""
    def __init__(self, question_id):
    self.question_id = question_id
    Rule.__init__(self)
    def base(self):
    return UserRule()
    def check(self):
    question = Question.query.filter(Question.id == self.question_id).first()
    return question and question.user_id == g.user.id
    def deny(self):
    abort(403)
    view raw rules.py hosted with ❤ by GitHub
    17 条回复    2019-01-26 09:39:28 +08:00
    loading
        1
    loading  
       2014-09-09 21:02:02 +08:00 via Android   ❤️ 1
    感谢
    humiaozuzu
        2
    humiaozuzu  
       2014-09-09 21:19:43 +08:00 via iPhone   ❤️ 1
    @loading 还有lz,有没有兴趣建立一个群或者邮件列表交流flask相关的技术,官方的邮件列表已经不怎么活跃了
    hustlzp
        3
    hustlzp  
    OP
       2014-09-09 21:34:05 +08:00
    @humiaozuzu 有兴趣啊,形式大家可以考虑一下。qq很少上...因为总觉得qq有点干扰...
    humiaozuzu
        4
    humiaozuzu  
       2014-09-09 21:52:01 +08:00
    @hustlzp 不如就用QQ吧,slack 之类的不太方便,邮件列表参与度挺难起来。 QQ群的话,当做异步交流的工具也未尝不可。
    tolbkni
        5
    tolbkni  
       2014-09-09 22:07:15 +08:00
    @humiaozuzu 没办法异步,除非一直开着 QQ,否则还是会丢失一堆信息的,而且不能按主题索引
    humiaozuzu
        6
    humiaozuzu  
       2014-09-09 22:11:51 +08:00
    @tolbkni 有方法异步,手机 QQ 里面可以设置不提醒,可以看到历史。我更希望讨论的结果能写成Blog,不仅仅是邮件列表的主题。而且 flask 的方向并不会太多,简单的讨论会更多些。
    hustlzp
        7
    hustlzp  
    OP
       2014-09-09 22:23:43 +08:00
    @humiaozuzu 可以试试 :)
    humiaozuzu
        8
    humiaozuzu  
       2014-09-09 22:53:56 +08:00
    @hustlzp
    @loading 发一下 QQ 吧
    hustlzp
        9
    hustlzp  
    OP
       2014-09-09 22:56:16 +08:00
    @humiaozuzu 724475543
    gonjay
        10
    gonjay  
       2014-09-09 23:02:59 +08:00   ❤️ 1
    可以参考下rails的cancan,那个gem质量还是相当不错的
    hustlzp
        11
    hustlzp  
    OP
       2014-09-09 23:29:04 +08:00
    @gonjay 恩恩,找时间看下 :)
    prowayne
        12
    prowayne  
       2015-05-18 19:22:36 +08:00
    装饰器模式怎么传参数进去呢
    QuestionAdminPermission(question.id)
    hustlzp
        13
    hustlzp  
    OP
       2015-05-20 09:52:02 +08:00   ❤️ 1
    @prowayne 这是个问题...

    目前只能在代码内部这样做:

    permission = QuestionAdminPermission(uid)
    if not permission.check():
    return permission.deny()
    elvis_w
        14
    elvis_w  
       2015-11-30 08:44:15 +08:00
    我是一直在用 Flask-Security
    https://pythonhosted.org/Flask-Security/
    fhefh
        15
    fhefh  
       2015-12-16 00:11:55 +08:00
    mark
    hsluoyz
        16
    hsluoyz  
       2019-01-26 09:39:12 +08:00
    现在新推出了一个权限框架,叫 PyCasbin。PyCasbin 采用了元模型的设计思想,支持多种经典的访问控制方案,如 ACL、RBAC、ABAC,还支持对 RESTful API 的控制。现在已经支持 Django、Flask 等 Web 框架了。需要中文文档的话,可以在百度搜索:PyCasbin
    hsluoyz
        17
    hsluoyz  
       2019-01-26 09:39:28 +08:00
    现在新推出了一个权限框架叫 PyCasbin。PyCasbin 采用了元模型的设计思想,支持多种经典的访问控制方案,如 ACL、RBAC、ABAC,还支持对 RESTful API 的控制。现在已经支持 Django、Flask 等 Web 框架了。需要中文文档的话,可以在百度搜索:PyCasbin
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1177 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 23:47 · PVG 07:47 · LAX 16:47 · JFK 19:47
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.