5

We have implemented a twisted web api.

To handle auth we have used a decorator that we wrap some routes with.

@requires_auth(roles=[Roles.Admin])
def get_secret_stuff(request):
    return 42

The requires_auth wrapper is implemented as follows.

def requires_auth(roles):
    def wrap(f):
        def wrapped_f(request, *args, **kwargs):
            # If the user is authenticated then...
            return f(request, *args, **kwargs)
        return wrapped_f
    return wrap

The issue is if there are multiple routes with this decorator, then a call to any of them results in the latest route to be decorated being called.

This is obviously not what I wanted and counter to my understanding of how decorators should work. I added some print statements to the code to try and figure it out:

def requires_auth(roles):
    def wrap(f):
        print(f) # This shows that the decorator is being called correctly once per each
                 # route that is decorated
        def wrapped_f(request, *args, **kwargs):
            # If the user is authenticated then...
            return f(request, *args, **kwargs)
        return wrapped_f
    return wrap

In case it is important, I am using twisted's inlineCallbacks for some of these routes, as well as twisted web's @app.route(url, methods) decorator for all of these routes.

Thank you for reading :)

EDIT: I removed the default argument to the constructor as I was told this was a bad idea :)

EDIT: Here is a minimal example that illustrates the problem:

from klein import Klein
import json
app = Klein()

def requires_auth(roles):
    def wrap(f):
        print('inside the first clojure with f=%s' % str(f))
        def wrapped_f(request, *args, **kwargs):
            print('inside the second closure with f=%s' % str(f))
            return f(request, *args, **kwargs)
        return wrapped_f
    return wrap

@app.route('/thing_a')
@requires_auth(roles=['user'])
def get_a(request):
    return json.dumps({'thing A': 'hello'})

@app.route('/thing_b')
@requires_auth(roles=['admin'])
def get_b(request):
    return json.dumps({'thing B': 'goodbye'})

app.run('0.0.0.0', 8080)

Going to the route '/thing_a' results in the json from route_b

4

3 回答 3

3

尝试这个:

from functools import wraps

def require_auth(roles=(Roles.USER,), *args, **kwargs):

    def call(f, *args, **kwargs):
        return f(*args, **kwargs)

    def deco(f):
        @wraps(f)
        def wrapped_f(request, *a, **kw):
            # do your authentication here
            return call(f, request, *a, **kw)

        return wrapped_f

    return deco
于 2013-09-16T17:58:34.107 回答
1

避免使用可变参数(例如列表)作为任何函数或方法的默认参数。更多关于为什么这是一个坏主意

我无法确认,但很有可能这是导致您的问题的原因。

编辑:如果我不清楚,我指的是

def requires_auth(roles=[Roles.USER]):

默认参数是可变的(列表)。

于 2013-09-16T16:15:41.743 回答
0

你需要以正确的顺序应用你的装饰器。这可能会起作用:

@route(...)
@requires_auth(roles=[Roles.Admin])
def get_secret_stuff(request):
    return 42

这可能不会:

@requires_auth(roles=[Roles.Admin])
@route(...)
def get_secret_stuff(request):
    return 42

因为分别,这些意味着

包装get_secret_stuff在授权者中,并将结果用作路由

get_secret_stuff用作路由,并将结果包装在授权方中。授权人永远不会进入路线。

于 2013-09-16T16:33:43.893 回答