如何正确地将参数从瓶子视图传递到装饰视图?

时间:2017-02-06 12:58:45

标签: python decorator bottle

我想用CSRF令牌保护我的观点,我采用的方法如下:

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)

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

@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

2 个答案:

答案 0 :(得分:1)

你在这里错过了一个级别的函数包装器。以下代码无需任何inspect黑客即可使用。

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装饰器和函数添加参数。

答案 1 :(得分:0)

简单的def delete_device():是否有效? (我不确定为什么你首先将db参数放在那里,因为你似乎没有使用它。)

编辑:def require_csrf(callback):怎么样? (我在那里删除了*args**kwargs,因为您不需要它们。)