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