Python decoder internally calls an invalid function

We have implemented a twisted website api.

To deal with auth, we used a decorator with which we port some routes.

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

The requires_auth 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 problem is that with this decorator there are several routes, and then calling any of them leads to the fact that the last route will be called, which will be decorated.

This is obviously not what I wanted, and contradicts my understanding of how decorators should work. I added some printing instructions to the code to try to understand:

 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 this is important, I use twisted inlineCallbacks for some of these routes, as well as the vinyl web decoder @app.route(url, methods) for all these routes.

Thanks for reading:)

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

EDIT: The following is a minimal example illustrating 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 route '/ thing_a' leads to json from route_b

+4
source share
3 answers

Try the following:

 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 
+3
source

Avoid using mutable arguments (like lists) as default parameters for any function or method. Learn more about why this is a bad idea .

I cannot confirm this, but there is a high probability that this is the cause of your problem.

EDIT: If I do not understand, I mean

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

default argument changed (list).

+1
source

You need to apply decorators in the correct order. This will probably work:

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

And that probably won't be:

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

Because, accordingly, they mean

Wrap get_secret_stuff in the authorizer and use the result as a route

and

Use get_secret_stuff as a route and wrap the result in the authorizer. The authorizer never gets into the route.

0
source

Source: https://habr.com/ru/post/1502450/


All Articles