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
Try this:
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
Avoid using mutable arguments (e.g. lists) as default parameters to any function or method. More on why this is a bad idea.
I can't confirm it, but there is a great possibility that this is what's causing your problem.
EDIT: In case I was unclear, I'm referring to
def requires_auth(roles=[Roles.USER]):
the default argument is mutable (a list).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With