I'm developing Flask application and I have a decorator called login_required which checks if the user is logged in, and sends the current user to the next function.
def login_required(function):
#wraps(function)
def decorator(*args, **kwargs):
token = None
if 'x-access-tokens' in request.headers:
token = request.headers['x-access-tokens']
if not token:
return jsonify({'message': 'a valid token is missing'}), 401
try:
data = jwt.decode(token, app.secret_key)
current_user = User.query.filter_by(username=data['username']).first()
except:
return jsonify({'message': 'token is invalid'}), 401
return function(current_user, *args, **kwargs)
return decorator
So, the callback function is declared like this.
#blueprint.route('/settings', methods=['GET'])
#login_required
def settings(current_user):
# here I check if the current_user is admin or not
...
# function body
Now I want to implement an admin_required decorator which depends on login_required decorator, to be easy using it within functions like this.
#blueprint.route('/settings', methods=['GET'])
#admin_required
def settings(current_user):
...
# function body
How can I achieve this?
So you can create your functionality like this
def decorator1(function):
def fun(*args, **kwargs):
user = function(*args, **kwargs)
print("here")
return user
return fun
def decorator2(function):
def fun(*args, **kwargs):
# firslty i will decorate function with decorator1
decor1 = decorator1(function)
decor1_result = decor1(*args, **kwargs)
# do operation with above result in your case now check if user is admin
print("here in decor2")
return decor1_result + 5
return fun
#decorator2
def test_function(a,b):
return a+b
# Now you want to access a+b and then add c to it
I have included comments for better understanding.
In your case decorator1 will be login_required decorator
and decorator2 will be admin_check decorator. So while creating admin check decorator you can access login_required decorator inside it.
Related
Using class based views. I have a decorator that retrieves headers for verification. However I get this error accessing the request headers in the decorator:
Decorator exception 'DetailsView' object has no attribute 'headers'
I must emphasize that accessing request headers in the view function works fine.
View function:
class DetailsView(View):
#check_access
def get(self, request):
res = {
'status': 200,
'response': 'heeaders test.',
'test': 'okay'
}
return HttpResponse(JsonResponse(res, safe=False), content_type='text/json')
Decorator:
def check_access():
def decorator(view_function):
def wrap(request, *args, **kwargs):
try:
print("headers", request.headers)
return view_function(request, *args, **kwargs)
except Exception as e:
return HttpResponse('Unauthorized', status=401)
return wrap
return decorator
Delete 'def check_access():' and Change decorate function to check_access.
And move 'self' argument.
in View Function
class DetailsView(View):
#check_access
def get(request): # Delete Self
....
in decorator
def check_access(view_function):
def wrap(self, request, *args, **kwargs): # add self
...
return wrap
referenced site : Decorator
I am using two decorators to decorate a function "add_user", the first one #auth_user authenticates the logged in user via json web token request headers and returns the user's information as an argument "user_info" back to the decorated function add_user. The second decorator #has_permission is supposed to check whether or not the user has permissions on the requested resource. I wanted to pass the user's information returned as user_info by #auth_user to #has_permission but don't know how to do that, here is the code so far:
#has_permission
#auth_user
def add_user(user_info):
"""
do something
"""
return None
The decorators:
def auth_user(f):
#wraps(f)
def wrapper(*args, **kwargs):
data = jwt.decode(request.headers['some-access-token'], some_secret_key)
try:
user_info = User.query.filter(User.id==data['user_id']).first()
except:
return jsonify({"message" : "error"}), 401
return f(user_info, *args, **kwargs)
return wrapper
and the second decorator where I want to access the user_info:
def has_permission(f):
wraps(f)
def wrapper(*args, **kwargs):
# This is where I want to access the user information instead of processing the key again #
# return some new_args ... #
return f(*args, **kwargs)
return wrapper
What is the best way to achieve this?
With some help, I managed to get this working and the main problem was the order/sequence of the decorators. In the question; #has_permission took #auth_user as an argument and #auth_user took the original function add_user as an argument like so:
has_permission(auth_user(add_user(user_info)))
This would mean that when arguments are returned, the outer most decorator #has_permission would return arguments to #auth_user and then to add_user. This meant that #has_permission could not access user_info returned by #auth_user so I modified the original function:
#auth_user
#has_permission
def add_user(user_info):
"""
do something
"""
return None
Now the decorators would be called in the correct order like this:
auth_user(has_permission(add_user(user_info)))
I also changed the way user_info was injected by #auth_user and changed it to kwargs instead of args to access it in #has_permission:
def auth_user(f):
#wraps(f)
def wrapper(*args, **kwargs):
data = jwt.decode(request.headers['some-access-token'], some_secret_key)
try:
user_info = User.query.filter(User.id==data['user_id']).first()
except:
return jsonify({"message" : "error"}), 401
kwargs['user_info'] = user_info
return f(*args, **kwargs)
return wrapper
And finally passing on the user_info to original function:
def has_permission(f):
wraps(f)
def wrapper(*args, **kwargs):
#access user info
user_info = kwargs['user_info']
return f(user_info, *args, **kwargs)
return wrapper
Hope this helps someone!
currently I have this code
#app.route("/protect1")
def protect1():
if not session.get('logged_in'):
session['next'] = "/protect1"
return render_template('login.html')
else:
return "This is the first protected page protect1"
#app.route("/protect2")
def protect2():
if not session.get('logged_in'):
session['next'] = "/protect2"
return render_template('login.html')
else:
return "This is the second protected page protect2"
in my flask app, everything working fine. Only it is not nice that I will need to repeat for each function (view) the if/else combination.
I would prefer to have some generic way, like this pseude-code:
#checklogin
#app.route("/protect1")
def protect1():
return "This is the first protected page protect1"
#checklogin
#app.route("/protect2")
def protect2():
return "This is the second protected page protect2"
One challenge here is that the #checklogin decorator would need to know the app.route path (e.g. "/protect1") in order to be able to set session['next'] correctly. I have no idea how to pass this parameter to the decorator, especially how to find it out in the first place. In other words, how does the function protect1() know that it is decorated with #app.route and which parameter ("/protect1") has been passed to that app.route decorator?
The decorator can look up the path on request; either using the URL that was loaded (available as request.url) or the request.endpoint attribute:
from functools import wraps
from flask import request, session
def checklogin(f):
#wraps(f)
def wrapper(*args, **kwargs):
if not session.get('logged_in'):
session['next'] = request.url
return render_template('login.html')
return f(*args, **kwargs)
return wrapper
Do place the decorator after the app.route() decorator or it'll not be registered as the handler for the route:
#app.route("/protect1")
#checklogin
def protect1():
return "This is the first protected page protect1"
I defined a check user method:
from functools import wraps
def check_user(func):
#wraps(func)
def wrapper(*args, **kwargs):
if session['logged_in']:
return func(*args, **kwargs)
else:
return 'Log in'
return wrapper
#app.route('/test')
#check_user
def test():
return "Hello"
It does not work. how can I correct it?
It seems you don't know how to create decorators in python. There are many helpful answers on this question: How can I make a chain of function decorators in Python?
Below is how you can create a decorator that checks if a user is logged in.
from functools import wraps
def checkuser(func):
"""Checks whether user is logged in or raises error 401."""
#wraps(func)
def wrapper(*args, **kwargs):
if not g.user:
abort(401)
return func(*args, **kwargs)
return wrapper
The decorator above will raise a 401 error if a user is not logged in. It will return the view function otherwise.
I have a login_require decorator that needs to set a session variable when email comes as QS.
decorators.py
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if session.get('is_logged', False):
email = request.args.get('email', False)
if not email:
session['my_var'] = 'Hello'
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated_function
views.py
#route('/')
#login_required
def home():
print session.get('my_var')
I am able to login and set the session var my_var for the login() view, but when I reach home() that var is None so I guess the decorator function is erasing it, but I know for sure it was set before in the login() view that the decorator redirected to.
How can I work it?