python - 如何正确地将参数从 Bottle View 传递到装饰 View ?

标签 python decorator bottle

我想用 CSRF token 保护我的 View ,我采用的方法如下:

from functools import partial
from bottle import jinja2_template as template,

def generate_csrf_token(length):
    '''Generate a random string using range [a-zA-Z0-9].'''
    chars = string.ascii_letters + string.digits
    return ''.join([choice(chars) for i in range(length)])


def require_csrf(callback, *args, **kwargs):
    def wrapper(*args, **kwargs):
        token = request.params.csrf_token
        if not token or token != global_vars['csrf_token']:
            abort(400)
        body = callback(*args, **kwargs)
        return body

    return wrapper

global_vars = {'BCC_VERSION': pkg_resources.get_distribution('bcc').version,
               'csrf_token': generate_csrf_token(48)}

j2template = partial(template, template_settings={'globals': global_vars})

@app.get("/remove/")
@require_csrf
def remove_device():
    ans = {'status': 200,
           'body': "csrf_token is: {}".format(global_vars['csrf_token'])}
    return HTTPResponse(**ans)

只要 View 不需要任何参数,这就可以正常工作。如果 View 接受数据库连接(例如使用插件时),事情就会变得棘手:

@app.get("/delete/")
@require_csrf
def delete_device(db):  # This causes the require_csrf decorator to fail
    ans = {'status': 200,
           'body': "csrf_token is: {}".format(global_vars['csrf_token'])}
    return HTTPResponse(**ans)

访问 /delete/ 时出现以下异常:

Traceback (most recent call last):
  File "/home/oznt/.virtualenvs/bcc/lib/python3.4/site-packages/bottle.py", line 979, in _handle
    out = route.call(**args)
  File "/home/oznt/.virtualenvs/bcc/lib/python3.4/site-packages/bottle.py", line 1949, in wrapper
    rv = callback(*a, **ka)
  File "/home/oznt/Software/bcc/bcc/views.py", line 32, in wrapper
    body = callback(*args, **kwargs)
TypeError: delete_device() missing 1 required positional argument: 'db'

为了解决这个问题,我将 require_csrf 装饰器稍微修改为:

def require_csrf(callback, *args, **kwargs):
    import inspect
    callback_args = inspect.getargspec(callback)[0]

    def wrapper(*args, **kwargs):
        token = request.params.csrf_token
        if not token or token != global_vars['csrf_token']:
            abort(400)
        body = callback(*callback_args, **kwargs)
        return body

    return wrapper

现在一切都按预期进行。但是,我不确定这是解决此问题的正确方法。您能否对此发表评论或针对该问题提出更好的解决方案?

更新

我尝试了世界末日的建议,但出现以下错误:

python3 main.py
Traceback (most recent call last):
  File "main.py", line 8, in <module>
    from bcc.views import app as home_app
  File "/home/oznt/Software/controller_configuration/bcc/views.py", line 66, in <module>
    @require_csrf()
TypeError: 'NoneType' object is not callable

最佳答案

您在这里缺少一层函数包装器。下面的代码在没有任何 inspect hack 的情况下工作。

def require_csrf():
    def decorator(callback):
        def wrapper(*args, **kwargs):
            token = request.params.csrf_token
            if not token or token != global_vars['csrf_token']:
                abort(400)
            return callback(*args, **kwargs)
        return wrapper
    return decorator

@app.get("/remove/<id>")
@require_csrf()
def remove_device(id):
    ans = {'status': 200,
           'body': "csrf_token is: {}".format(global_vars['csrf_token'])}
    return HTTPResponse(**ans)

另请注意,如果需要,您可以向 require_csrf 装饰器和函数添加参数。

关于python - 如何正确地将参数从 Bottle View 传递到装饰 View ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42068410/

相关文章:

python - 如何将子模块名称保留在 Python 包的 namespace 之外?

python - 检查函数是否使用@classmethod

jsp - 从 Sitemesh 装饰器传递一个变量

python - Beaker 作为 session 中间件

python - 我应该如何在 Bottle 应用程序中使用 sqlalchemy session 来避免 'Lost connection to MySQL server during query'

python - Bottle 模板 : how to import a python package

python - uwsgi - 不使用 virtualenv 中的 python2.7.3,而是使用 venv 中的 2.6,即使 2.6 仅在全局安装

python - 获取另一列中每个唯一值的前 2 个值

python - 使用pdb以简单的方式调试python代码

python - 切换装饰器