通过函数名找出烧瓶终点的正确方法是什么?

时间:2016-01-06 03:33:37

标签: python flask decorator access-control blueprint

我有一个烧瓶演示,它需要访问控制。我的访问控制基于函数名称,我写了一个装饰器来做它。

装饰者定义是:

def permission(fn):
    @wraps(fn)
    def wraped(*args, **kwargs):
        if _do_validate(fn.__name__):  # TODO do check user has privileges here
            return fn(*args, **kwargs)
        else:
            abort(401)
    return wraped

用法示例如下:

    @app.route('/index')
    @permission
    def index():
        return 'hello world'

如果用户没有指定端点,这项工作正常,因为Flask的默认端点是fn.__name__,在我的示例中是endpoint='index'

但是当用户指定端点或仅使用蓝图时,端点已更改。例如:

    bl = Blueprint('admin', __name__)

    @bl.route('hello')
    @permission
    def hello():
        return 'hello world in blueprint'

端点更改为admin.hello

我不想在@permission中指定任何arg,所以我写了一个新的权限装饰器,如下所示:

def permission(fn):
    @wraps(fn)
    def wraped(*args, **kwargs):
        m = fn.__module__.split('.')
        if len(m) <= 2:
            # app must define in the root module, so if user not use blueprint, juse use fn.__name__ as endpoint
            reg_f = fn.__name__
        else:
            # blue print must define in the submodule
            reg_f = '{0}.{1}'.format(m[1], fn.__name__)

        if _do_validate(ref_f):  # TODO do check user has privileges here
            return fn(*args, **kwargs)
        else:
            abort(401)
    return wraped

问题解决了,但我觉得它不优雅。

有人能给我一个更好的吗? THX。

1 个答案:

答案 0 :(得分:0)

如果蓝图的名称不等于模块__name__,则您的解决方案无效。

我可以提出下一个解决方案:

from collections import defaultdict
registered = defaultdict(int)

def permission(fn):
    registered[fn.__name__] += 1
    fn.permission_token = "%s.%s" % (fn.__name__, registered[fn.__name__])

    @wraps(fn)
    def wraped(*args, **kwargs):
        if _do_validate(fn.permission_token):
            return fn(*args, **kwargs)
        else:
            abort(401)
    return wraped

@permission
def foo():
    pass

@permission
def bar():
    pass

def _do_validate(permission_token):
    return {foo.permission_token: True,
            bar.permission_token: False}.get(permission_token, False)

它的主要缺点是您必须导入所有路线 _do_validate创建此类&#34;访问控制列表&#34;的模块。

UPD:好的,因为你想使用端点值检查用户访问权限,下一个解决方案允许按视图功能查找端点:

from collections import defaultdict
from flask import current_app

def find_endpoint(fn):
    # This loop can be optimized by maintaining
    # {permission_token => endpoint} dict.
    for endpoint, view_func in current_app.view_functions.items():
        if getattr(view_func, 'permission_token', None) == fn.permission_token:
            return endpoint
    return None

registered = defaultdict(int)

def permission(fn):
    registered[fn.__name__] += 1

    @wraps(fn)
    def wrapped(*args, **kwargs):
        endpoint = find_endpoint(wrapped)
        assert endpoint is not None
        if _do_validate(endpoint):
            return fn(*args, **kwargs)
        else:
            abort(401)

    # This will work because flask's route decorator returns
    # an original view function, not the wrapped one.
    wrapped.permission_token = "%s.%s" % (fn.__name__, registered[fn.__name__])
    return wrapped

def _do_validate(endpoint):
    # Or search in the DB
    return {'index': True,
            'bp.index': False}.get(endpoint, False)

然而,检查端点访问并不是一个好主意,因为在开发过程中任何代码更改都可能导致意外丢失(甚至更糟 - 检索!)访问。所以,现在我的意见是,为action装饰器使用额外的resource(或permission)参数将是合理的。使用此参数,您可以自由修改端点/视图功能,而不会产生任何不良副作用。

UPD2:实际上,在包装函数中查找端点值的更简单方法就是使用request.endpoint