access django session from a decorator - python

I have a decorator that I use for my views #valid_session
from django.http import Http404
def valid_session(the_func):
"""
function to check if the user has a valid session
"""
def _decorated(*args, **kwargs):
if ## check if username is in the request.session:
raise Http404('not logged in.')
else:
return the_func(*args, **kwargs)
return _decorated
I would like to access my session in my decoartor. When user is logged in, I put the username in my session.

Will something like the following solve your problem:
def valid_session(func):
def decorated(request, *args, **kwargs):
print request.session
return func(request, *args, **kwargs)
return decorated

The view function takes the request as the first parameter, so the decorator will receive it as its first parameter as well. You can pull the session out of it with just request.session.

You could pass the request (or just the session) in as a parameter to the decorator. I just don't know how to get at it to pass it in. I was trying to figure out something similar last night.

Related

Flask-Login How to check if a user has a certain attribute value

i am currently using flask-login for the Authentication of my website.
My Model is a simple User (with id, mail etc) which has an admin(boolean) attribute.
I dont need more than 2 Roles.
Is there any way to check after a route was called, if the current user has the value "True" at the admin attribute?
I tried solving this issue with trying out custom decorator, as well as accessing session['admin'] , but it did not work.
As #Agung Wiyono commented Flask-Login provides a current_user variable, see docs.
In your route you can do something like:
from flask_login import current_user
#app.route("/test")
#login_required
def test():
if current_user.admin:
print('user is admin')
# blah blah
If you don't want to use the login_required decorator:
#app.route("/test")
def test():
if current_user.is_authenticated and current_user.admin:
print('user is authenticated and is an admin')
# blah blah
If you want to use a decorator to check if the current_user admin value is True:
def admin_role_required(func):
#wraps(func)
def decorated_view(*args, **kwargs):
if request.method in EXEMPT_METHODS:
return func(*args, **kwargs)
elif not current_user.admin:
abort(403)
return func(*args, **kwargs)
return decorated_view
This code is more or less the code of #login_required, except it checks the state of the admin attribute - see source.
And use as below. Note the order of the decorators is important. #login_required is called first then #admin_role_required is called. Decorator admin_role_required assumes the current_user is already authenticated. If admin_role_required was called first then the current_user proxy would not have an attribute admin and you'd have an error.
#app.route("/test")
#admin_role_required
#login_required
def test():
# need to be logged in and admin be True to get here
# blah blah

decorator to change view function logic if user is anonymous

I currently have a view that returns different responses depending on if the user is anonymous or not, atm I'm using:
#app.route("/anonymous")
def anonymous_page():
if current_user.is_anonymous:
return render_template("anonymous_page.html")
return render_template("user_page.html")
I want to make a decorator, let's call it #show_only_to_anonymous, that can be used on a normal view function with a single return statement, that will do something like this:
#app.route("/anonymous")
#show_only_to_anonymous
# if the user is anonymous, return the decorated function's view,
# elif the user is NOT anonymous, return "user_page.html"
def anonymous_page():
return render_template("anonymous_page.html")
I know there is a login_required decorator, I went to take a look at the source code here, and it uses LocalProxy for current_user under the hood, tried to replicate that and failed,
I guess my understanding of the flask framework is lacking as I am kinda new to it, anyone has an idea?
edit:
I managed to get it working by looking at the source code of flask-login, the file is: flask_login/utils.py, I used the same flask and werkzeug.local functions and other objects and created a new file in my project which I named custom_decorators.py and imported as a module in my routes.py file, these are the contents of custom_decorators.py:
from functools import wraps
from werkzeug.local import LocalProxy
from flask import _request_ctx_stack, has_request_context, current_app
current_user = LocalProxy(lambda: _get_user())
def _get_user():
if has_request_context() and not hasattr(_request_ctx_stack.top, 'user'):
current_app.login_manager._load_user()
return getattr(_request_ctx_stack.top, 'user', None)
def show_only_to_anonymous(func):
#wraps(func)
def decorated_view(*args, **kwargs):
if not current_user.is_anonymous:
return current_app.view_functions['show_to_known_users'](*args, **kwargs)
return func(*args, **kwargs)
return decorated_view
now in my routes.py I can do this:
# this page can only be seen by anonymous users
#app.route("/anonymous")
#show_only_to_anonymous
def anonymous_page():
return render_template("anonymous_page.html")
# this will be called if a logged in user goes to mywebsite.com/anonymous:
#app.route("/show_to_known_users")
def show_to_known_users():
return redirect(url_for("user_page.html", code=301))
this solution feels a bit 'hacky', is there a better way?

Check Flask cookie for user_id and obtain User if it exists or redirect if not

What I'm after is something like what the #login_required decorators accomplish but I'm not sure if a custom decorator would allow me to pass the User option back to my route function. There are several pages in my app that require the user to be logged in to access them so I am looking for the most efficient way/least code to copy into each access-restricted route that will verify they have a user_id in their cookie (logged in), cache get/query for their User object using the user_id, and carry on with the route function, else redirect to the login page if user_id is not present.
What I was hoping to do was something like:
#noteBP.route('/note', methods=['GET', 'POST'])
def new_note():
user_details = return_user_details_if_logged_in_else_redirect_to_login_url(next=request.url)
...
And that function would check for the user_id in the session cookie and send back the User object or redirect to the login page:
def return_user_details_if_logged_in_else_redirect_to_login_url(next=None):
user_id = session.get('user_id')
if user_id:
user_details = user_util.get_user_details_by_id(user_id)
return user_details
else:
return redirect(url_for('usersBP.login', next=next))
Turns out, redirect does not work the same way as Abort where it gets called even if you are inside another function so now I have do do additional processing back in the route function to check:
user_details = return_user_details_if_logged_in_else_redirect_to_login_url(next=request.url)
if not user_details:
return redirect(redirect_url)
I'm looking to avoid having to paste this chunk of code at the top of every access-restricted route. Is there a more efficient way/DRY approach to do this? if with a decorator, how do I get the user_details into the route function?
If you want to redirect in a function called inside a view, raise a RequestRedirect. If you want to redirect in a decorator, check if the user is not logged in and return a redirect rather than the actual view (or use the previous function to raise the redirect).
import functools
from flask import url_for, redirect, g
from werkzeug.routing import RequestRedirect
def require_login():
if g.user is None:
raise RequestRedirect(url_for('login'))
def login_required(view):
#functools.wraps(view)
def wrapped_view(**kwargs):
require_login()
# or
# if g.user is None:
# return redirect(url_for('login'))
return view(**kwargs)
return wrapped_view
#app.route('/secret2')
#login_required
def secret1():
return 'secret 1'
#app.route('/secret2')
def secret2():
require_login()
return 'secret 2'
Populate g.user in a before_request handler.
from flask import session
#app.before_request
def load_user():
g.user = None
if 'user_id' in session:
# use whatever caching logic you want here.
g.user = User.query.get(session['user_id'])
Populate session['user_id'] in your login view.
#app.route('/login')
def login():
if request.method == 'POST':
user = User.query.filter_by(username=request.form['username']).first()
if user and user.check_password(request.form['password']:
session['user_id'] = user.id
return redirect(url_for('index'))
return render_template('login.html')
Now you can access g.user from any route, without passing it explicitly. If you do want to pass it explicitly, modify login_required.
def require_login():
if g.user is None:
raise RequestRedirect(url_for('login'))
return g.user
def login_required(view):
#functools.wraps
def wrapped_view(**kwargs):
user = require_login()
return view(user, **kwargs)
return wrapped_view
#app.route('/secret')
def secret(user):
return 'user {} is logged in'.format(user.id)
Give that all of this except passing the user is part of Flask-Login, you should really reconsider using Flask-Login instead of trying to maintain your own solution.
A decorator is a function that wraps and replaces another function. Since the original function is replaced, you need to remember to copy the original function’s information to the new function. Use functools.wraps() to handle this for you.
This example assumes that the login page is called 'login' and that the current user is stored in g.user and is None if there is no-one logged in.
from functools import wraps
from flask import g, request, redirect, url_for
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if g.user is None:
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
To use the decorator, apply it as innermost decorator to a view function. When applying further decorators, always remember that the route() decorator is the outermost.
#app.route('/secret_page')
#login_required
def secret_page():
pass
Note:
The next value will exist in request.args after a GET request for the login page. You’ll have to pass it along when sending the POST request from the login form. You can do this with a hidden input tag, then retrieve it from request.form when logging the user in.
<input type="hidden" value="{{ request.args.get('next', '') }}"/>

Trouble understanding decorator

I want to check if the user is authenticated before he/she can access the method, so I wrote a decorator named authorize but only the decorator code is executed and even if the user is authenticated, the method is not called after it.
here are the method and the decorator codes:
#authorize
def post(self, **kw):
# store data in database after authentication done using #authorize
def authorize(f):
def wrapper(*args, **kwargs):
secret_key = config.get('auth_secret_key')
auth_message = config.get('auth_message')
if 'HTTP_TOKEN' not in request.environ:
abort(401, detail='Authentication failed', passthrough='json')
gibberish = request.environ['HTTP_TOKEN']
if triple_des(secret_key).decrypt(gibberish, padmode=2).decode() != auth_message:
abort(401, detail='Authentication failed', passthrough='json')
return wrapper
If user has authentication problem, 401 is raised and request is aborted but if he is authenticated, the post method is not called. BTW, its my first time writing decorator so I might be completely wrong. Thanks for any answers
You need to actually call the function within your wrapper.
f(*args, **kwargs)

Nested decorators, raising 404

Suppose I have view
def foo(request)
I'm also using custom user model as follow:
class MyUser(AbstractUser):
field = models.BooleanField()
I'd like to combine 2 django decorators: login_required and user_passed_test, so that anonymous user should be redirected to a login page and user who is not allowed to see the view (user.field == False) should see a proper message (say, something like 'you're not allowed to see this').
So I tried:
my_decor = user_passes_test(lambda user: user.field == True,
login_url='/bar')
def custom_decor(view_func):
decorated_view_func = login_required(my_decor(view_func), login_url='/login')
return decorated_view_func
And I also have to define view:
def bar(request):
return HttpResponse('you are not allowed to see this context.')
and hardcode it in my urls.
The question is: can I do this without creating an additional view and adding it to urls? Is there a way to make 'user_passed_test' decorator raising an 404 error instead of redirecting to a login view?
There is probably a very simple solution and surely I'm just missing it.
I dont remember very well the decorators syntax, but you can do:
def custom_decor(view_func):
def decorator(request, *args, **kwargs)
if request.user.field is True:
return view_func(request, *arg, **kwargs)
raise Http404
return decorator
so...
#login_required(login_url='/login')
#custom_decor
def foo(request)
# ...

Categories

Resources