为了账号安全,请及时绑定邮箱和手机立即绑定

检测路由是否不是最外层/Flask 中装饰器的错误“顺序”

检测路由是否不是最外层/Flask 中装饰器的错误“顺序”

拉丁的传说 2021-08-17 16:29:56
由于@route装饰器必须使用当前提供给装饰器的回调来注册视图,因此它必须是最外层的装饰器才能在处理请求时接收正确的调用函数。这会产生一种可能的情况,即视图已被装饰,但由于装饰器的顺序错误,因此不会调用被装饰的函数。如果用于装饰需要用户登录、具有特定角色或具有特定标志的视图,则该检查将被静默忽略。我们当前的解决方法是让标准操作是拒绝对资源的访问,然后需要一个装饰器来允许访问。在这种情况下,如果在处理请求时未调用装饰器,则请求将失败。但是在某些用例中,这会变得很麻烦,因为它需要您装饰所有视图,除了少数应该免除的视图。对于纯粹的分层布局,这可能有效,但对于检查单个标志,结构可能会变得复杂。有没有一种正确的方法来检测我们在装饰层次结构中的有用位置被调用?即我们可以检测到还没有一个route装饰器应用于我们要包装的函数吗?# wrapped in wrong order - @require_administrator should be after @app.route@require_administrator@app.route('/users', methods=['GET'])实现为:def require_administrator(func):    @functools.wraps(func)    def has_administrator(*args, **kwargs):        if not getattr(g, 'user') or not g.user.is_administrator:            abort(403)        return func(*args, **kwargs)    return has_administrator在这里,我想检测我的自定义装饰器是否被包装在 之后@app.route,因此,在处理请求时永远不会被调用。Usingfunctools.wraps在所有方面都用新的函数替换了被包装的函数,因此查看__name__要被包装的函数将失败。这也发生在装饰器包装过程的每一步。我试过同时查看tracebackand inspect,但没有找到任何确定序列是否正确的体面方法。
查看完整描述

2 回答

?
子衿沉夜

TA贡献1828条经验 获得超3个赞

我正在添加另一个答案,因为现在我有一些最少的 hacky(阅读:我正在使用检查来读取给定函数的源代码,而不是自己读取整个文件),跨模块工作,并且可以重用于任何其他应该始终是最后一个的装饰器。您也不必app.route像在我的其他答案的更新中那样使用不同的语法。


这是如何做到这一点(警告:这是一个非常封闭的开始):


import flask

import inspect



class DecoratorOrderError(TypeError):

    pass



def assert_last_decorator(final_decorator):

    """

    Converts a decorator so that an exception is raised when it is not the last    decorator to be used on a function.

    This only works for decorator syntax, not if somebody explicitly uses the decorator, e.g.

    final_decorator = some_other_decorator(final_decorator) will still work without an exception.


    :param final_decorator: The decorator that should be made final.

    :return: The same decorator, but it checks that it is the last one before calling the inner function.

    """

    def check_decorator_order(func):

        # Use inspect to read the code of the function

        code, _ = inspect.getsourcelines(func)

        decorators = []

        for line in code:

            if line.startswith("@"):

                decorators.append(line)

            else:

                break


        # Remove the "@", function calls, and any object calls, such as "app.route". We just want the name of the decorator function (e.g. "route")

        decorator_names_only = [dec.replace("@", "").split("(")[0].split(".")[-1] for dec in decorators]

        is_final_decorator = [final_decorator.__name__ == name for name in decorator_names_only]

        num_finals = sum(is_final_decorator)


        if num_finals > 1 or (num_finals == 1 and not is_final_decorator[0]):

            raise DecoratorOrderError(f"'{final_decorator.__name__}' is not the topmost decorator of function '{func.__name__}'")


        return func


    def handle_arguments(*args, **kwargs):

        # Used to pass the arguments to the final decorator


        def handle_function(f):

            # Which function should be decorated by the final decorator?

            return final_decorator(*args, **kwargs)(check_decorator_order(f))


        return handle_function


    return handle_arguments

您现在可以app.route使用应用于该函数的该函数替换该app.route函数。这很重要,必须在使用app.route装饰器之前完成,所以我建议在创建应用程序时执行。


app = flask.Flask(__name__)

app.route = assert_last_decorator(app.route)



def require_administrator(func):

    @functools.wraps(func)

    def has_administrator(*args, **kwargs):

        print("Would check admin now")


        return func(*args, **kwargs)


    return has_administrator



@app.route("/good", methods=["GET"])  # Works

@require_administrator

def test_good():

    return "ok"


@require_administrator

@app.route("/bad", methods=["GET"])  # Raises an Exception

def test_bad():

    return "not ok"

我相信这几乎是您在问题中想要的。


查看完整回答
反对 回复 2021-08-17
  • 2 回答
  • 0 关注
  • 170 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信