I'm working on flask RESTful application that uses auth0 for authentication and authorization. Then, I wrote a decorator that validates the token and extract user's id from it. My goal is use that id, extracted from token, to used inside the decorated function, and throw an exception if user's id from token and from URL parameter doesn't match. This is aimed to avoid users to change data of another user, with his own token. I'm not sure this is the best practice for a RESTful app, but seems to be needed in my case.
That said, I trying to figure out the best approach pass the user's id from token, to the decorated funcion:
Something like this:
def authorization():
def inner(func):
def wrapper(*args, **kwargs):
try:
"""
token validation stuff
...
"""
wrapper.user_id = token_payload['user_id']
except Exception:
return {"success": False}, 500
return func(*args, **kwargs)
return wrapper
return inner
#authorization()
#app.route('/test', methods=['GET'])
def some_function():
return jsonify({
'success': True,
'user_id': some_function.user_id
})
As you can see, I setting the user_id field to the wrapper function, which seems to not be the best way, to do it. Is there any different approach to this situation? maybe using Flask resources?
You can skip one level of wrapping in your decorator since you're not giving it any parameters.
Also, I'd just pass the extracted id into the wrapped function directly instead of setting an attribute.
And finally, you should add the auth decorater innermost, since the decorated function is what you want to register with Flask.
Extra: use functools.wraps to update the signature of your wrapped function, to make introspection and debugging easier.
Thus:
from functools import wraps
def authorized(func):
#wraps(func)
def wrapper(*args, **kw):
try:
# token stuff
user_id = token_payload['user_id']
except:
return jsonify({"success": False}), 500
return func(user_id, *args, **kw)
return wrapper
#app.route('/test', methods=['GET'])
#authorized
def some_function(user_id):
return jsonify({
'success': True,
'user_id': user_id
})
Now every function you decorate with #authorized will need to have user_id as their first parameter, and everything should work as you expect.
Related
#property
def get_maca(self, request):
if request.user.name == "example":
return self
I want to do something like this. If the user name is example return that object.
How to access the request like this?
The standard way is to pass the request, or in your case just the user object, from the view/router all the way down to the models.
This gets very quickly out of hand in a larger project, so my approach is to use thread local to save some of the request context that I like to have available across the whole project. The thread local storage will keep data available inside a single thread, without it being accessible from other threads - great if you're gonna run the Django app on a production server.
Start with the local storage:
from threading import local
_active_user = local()
def activate_user(user):
if not user:
return
_active_user.value = user
def deactivate_user():
if hasattr(_active_user, "value"):
del _active_user.value
def get_user():
"""Returns `(is_anonymous, user)` ."""
active_user = getattr(_active_user, "value", None)
if active_user and active_user is not AnonymousUser:
try:
return False, active_user
except AttributeError:
pass
return True, None
Now that's all good, you can use this manually. Calling activate_user will make you be able to call get_user in any place in your project. However, this is error prone - if you forget to call deactivate_user, the user object will still be available to the next coming request.
The rest of the answer is to show how to make things automatic.
Let's first make a middleware to clean up by calling deactivate_user after every single request.
class ThreadCleanupMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
# Executed for each request/response after
# the view is called.
deactivate_user()
return response
Add a path to the ThreadCleanupMiddleware to the end of your settings.MIDDLEWARE list.
Finish up with a view mixin that activates the user automatically (that's for class based views; if you're using functional views, it would be a decorator instead):
class ContextViewSetMixin:
def initial(self, request, *args, **kwargs):
super().initial(request, *args, **kwargs)
if request.user.is_authenticated:
activate_user(request.user)
class ContextModelViewSet(ContextViewSetMixin, viewsets.ModelViewSet):
pass
I am trying to build a flask app which will be having RBAC feature. For this I have written a decorator which is working fine but it can only take one argument meaning only one access level(e.g WRITE, READ, admin etc), but I want to pass multiple arguments to it. I have tried passing a list but its not taking it. I have never worked with decorators so need help with it. Thanks.
def permission_required(permission):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if not current_user.can(permission):
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
def admin_required(f):
return permission_required(Permission.ADMIN)(f)
I as passing it like this:
#role_needed(Permission.VIEW), but I want to have this #role_needed(Permission.VIEW, Permission.WRITE)
My permission class is like this:
class Permission:
VIEW = 'Vew'
WRITE = 'Write'
ADMIN = 'admin'
First, I'd advise that you have a look at some tutorial on decorators, they are pretty cool and you definitely need to understand the basics if you want to use flask. I personally quite like this RealPython tutorial.
Second, you have two solutions : either default second argument or argument packing.
def permission_required(permission1, permission2=None):
...
or
def permission_required(*perms):
...
I personaly way prefer the second option.
Example:
def permission_required(*perms):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kwargs):
for perm in perms:
if not current_user.can(perm):
abort(403)
return f(*args, **kwargs)
return decorated_function
return decorator
I think you missed the point that decorators are just usual functions, taking a function in argument and another one, the later being by design a wrapper around the original one. In your case, permission_required is a decorator factory, that can be used to specialize a decorator based on input arguments. So all you need to do is to allow passing any number of arguments to your decorator factory:
def role_needed(*permissions):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kwargs):
nonlocal permissions # Just to make sure `permission` is available in this scope
# Implement here how to deal with permissions
return f(*args, **kwargs)
return decorated_function
return decorator
which can be called as intended:
#role_needed(Permission.VIEW, Permission.WRITE, ...)
In the function, permissions will store the input Permission as a Python tuple object.
I have this url route, can I get user_id with flask.request?
I want to create a wrapper, and get the user_id here.
def test_required(fn):
#wraps(fn)
def wrapper(*args, **kwargs):
user_id = ?????
return fn(*args, **kwargs)
return wrapper
#app.route('/api/test/<int:user_id>', methods=['GET'])
#test_required
def jwt_routes_test(user_id):
request.args, request.form, or request.values not return this value.
Can I access it somehow?
This code is only valid with a view function that accepts user_id as an argument. So, you simply get it from there.
Use it as a parameter in your route:
#app.route('/api/test/<int:user_id>', methods=['GET'])
def myroute(user_id: int):
# do something
https://flask.palletsprojects.com/en/1.0.x/quickstart/#variable-rules
Looks like you're asking for view_args dictionary.
A dict of view arguments that matched the request. If an exception
happened when matching, this will be None.
I've seen the posts on passing GET parameters and hardcoded parameters here and here.
What I am trying to do is pass POST parameters to a custom decorator. The route is not actually rendering a page but rather processing some stuff and sending the results back through an AJAX call.
The decorator looks like this:
# app/util.py
from functools import wraps
from models import data
# custom decorator to validate symbol
def symbol_valid():
def decorator(func):
#wraps(func)
def decorated_function(symbol, *args, **kwargs):
if not data.validate_symbol(symbol):
return jsonify({'status': 'fail'})
return func(*args, **kwargs)
return decorated_function
return decorator
The view looks something like this:
# app/views/matrix_blueprint.py
from flask import Blueprint, request, jsonify
from ..models import data
from ..util import symbol_valid
matrix_blueprint = Blueprint('matrix_blueprint', __name__)
# routing for the ajax call to return symbol details
#matrix_blueprint.route('/route_line', methods=['POST'])
#symbol_valid
def route_line():
symbol = request.form['symbol'].upper()
result = data.get_information(symbol)
return jsonify(**result)
I understand that I can actually call #symbol_valid() when I pass a parameter through GET like this /quote_line/<symbol> but I need to POST.
The question then is how can my decorator access the POSTed variable?
Simple solution. Imported Flask's request module into the util.py module which contains the decorator. Removed the outer function as well.
See code:
# app/util.py
from flask import request # <- added
from functools import wraps
from models import data
# custom decorator to validate symbol
def symbol_valid(func):
#wraps(func)
def decorated_function(*args, **kwargs): # <- removed symbol arg
symbol = request.form['symbol'] # <- paramter is in the request object
if not data.validate_symbol(symbol):
return jsonify({'status': 'fail'})
return func(*args, **kwargs)
return symbol_valid
The decorator accept a func parameter. You must use your decorator like #symbol_valid() or make the function symbol_valid accept a func parameter.
If you are doing it right, you can access the request object anywhere during the request cycle. It just works.
I want my authorization decorator to be able to pass a custom user object to the view that it decorates.
Currently, I am having the decorator set an attribute on flask.g to do this. Is this acceptable use of flask.g or is there a better way?
My code looks something like this:
def auth(f):
#wraps(f):
def decorated(*args, **kwargs):
user = getUserObj(request.headers.get('user'), request.headers.get('pass'))
flask.g.user = user
return f(*args, **kwargs)
return decorated
And then the view is:
#api.route('/info')
#auth
def info():
flask.g.user # this contains my user object now