I have a menu where users can see different menu items depending on their permissions. I check whether the users has the option or not when logging in, store it in a global variable, and check that variable when rendering the menu.
In production, the menu only shows up correctly about 50% of the time. Sometimes the value is set, sometimes it is empty. Why isn't this working correctly?
#app.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user = User.get(request.form['username'])
if user.user is None:
return redirect('/login/')
if user and check_password_hash(user.user.password, request.form['password']):
login_user(user)
if isinstance(current_user.user, UserTypeOne):
group = UserGroup.query.filter_by(id=int(current_user.user.group)).first()
app.jinja_env.globals['group_access_to_feature_one'] = group.group_access_to_feature_one
return redirect(request.args.get('next') or url_for('index'))
return redirect(url_for('login'))
The header template has a conditional to check that value:
{% if group_access_to_feature_one%}<ul>...</ul>{%endif%}
When debugging, I put {{ group_access_to_feature_one }} to see the value, and sometimes it is True and sometimes it's empty.
In production (and possibly sometimes on dev), you are running with multiple processes. Each process creates its own copy of the app, and so only the app in the process that handled the login request will see the changes to the env. This is one of the main reasons using Python globals to store state is discouraged. app.jinja_env.globals is meant to be modified only during setup so that each process/thread remains consistent.
Use a database or other storage such as redis to store and access global state. Use the session to store information about a specific browser session, such as the logged in user. Load the state for each user on each request using an app.before_request callback.
Your code is also incorrect because the global Flask environment is changed every time a user logs in. So the value of the value for every user will be based on the last user to log in and set the value.
Related
I would like to know how to restrict access to certain pages of my website using sessions. In, summery I'd like the administrator to log in through the same login form as normal users but using the credentials he entered, he'll be redirected to the backend page where he'll have full access to both front-end web pages and back-end web pages. When a user logs in, they can only view front-end web pages. I'm trying to avoid a situation where a user logs in and then changes the URL in the search bar of the web browser from 127.0.0.7/homepage to 127.0.0.7/backend and is allowed access because he's logged in.
I'm using the code below to check if the user is logged in. If they are not then they're redirected to the login page. I'm using flask framework. Any suggestions are greatly appreciated.
Thank you.
app.route('/backend', methods=['POST', 'GET'])
def backend():
if 'userkey' in session:
# connect to database using pymysql
if cursor.rowcount == 0:
return render_template('backend.html', msg="No orders found...")
else:
rows = cursor.fetchall()
return render_template('backend.html', orderdata=rows)
elif 'userkey' not in session:
return redirect('/login')
else:
return redirect('/login')
You must already be setting session['userkey'] in your login view once the user has authenticated, you just need to add the additional information about the users admin status to get this working. It would also be much easier if you moved all of the if 'userkey' in session checks outside of your view function into a decorator so that it can easily be reused to protect all of your view functions. This pattern is shown in the flask documentation under the heading 'Login Required Decorator' http://flask.pocoo.org/docs/1.0/patterns/viewdecorators/.
In your login function you should query your database for the users admin status at the same time as retrieving the userkey and hashed password. Then simply set session["admin"] = True if the user is an admin or don't set this at all if they are not an admin.
The following code would then work for the decorator.
from functools import wraps
from flask import session
def login_required(status=None):
def login_decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
if 'userkey' in session and (status is None or status in session):
return func(*args, **kwargs)
else:
return redirect("/login")
return wrapper
return login_decorator
This decorator should then be applied to protect all of your view functions. It will then be executed prior to each view function either allowing the view to proceed if all the conditions are met or redirecting the user back to the login page if not. It takes an optional single argument status which in your case should be 'admin' for all of the back end views and should not be supplied for the front end views that do not require admin privileges. The functools.wraps decorator inside the function is just there to update the metadata of the wrapped function so that attributes like function.__name__ behave as expected. The 'userkey' in session check is the same as in your original code but we additionally check if a status was supplied and if so if it is also stored in the session. In your case the only value of status would be 'admin' but this pattern could easily be expanded to allow other groups that would each have access to different parts of the website.
Give that all the user checks are carried out in the decorator your backend view function can be simplified to
#app.route('/backend', methods=['POST', 'GET'])
#login_required("admin")
def backend():
# connect to database using pymysql
if cursor.rowcount == 0:
return render_template('backend.html', msg="No orders found...")
else:
rows = cursor.fetchall()
return render_template('backend.html', orderdata=rows)
The same decorator can be used to protect all of your front end view functions, just omit the "admin" argument.
#app.route('/frontend', methods=['POST', 'GET'])
#login_required()
def frontend():
# .....
I have a menu where users can see different menu items depending on their permissions. I check whether the users has the option or not when logging in, store it in a global variable, and check that variable when rendering the menu.
In production, the menu only shows up correctly about 50% of the time. Sometimes the value is set, sometimes it is empty. Why isn't this working correctly?
#app.route('/login/', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user = User.get(request.form['username'])
if user.user is None:
return redirect('/login/')
if user and check_password_hash(user.user.password, request.form['password']):
login_user(user)
if isinstance(current_user.user, UserTypeOne):
group = UserGroup.query.filter_by(id=int(current_user.user.group)).first()
app.jinja_env.globals['group_access_to_feature_one'] = group.group_access_to_feature_one
return redirect(request.args.get('next') or url_for('index'))
return redirect(url_for('login'))
The header template has a conditional to check that value:
{% if group_access_to_feature_one%}<ul>...</ul>{%endif%}
When debugging, I put {{ group_access_to_feature_one }} to see the value, and sometimes it is True and sometimes it's empty.
In production (and possibly sometimes on dev), you are running with multiple processes. Each process creates its own copy of the app, and so only the app in the process that handled the login request will see the changes to the env. This is one of the main reasons using Python globals to store state is discouraged. app.jinja_env.globals is meant to be modified only during setup so that each process/thread remains consistent.
Use a database or other storage such as redis to store and access global state. Use the session to store information about a specific browser session, such as the logged in user. Load the state for each user on each request using an app.before_request callback.
Your code is also incorrect because the global Flask environment is changed every time a user logs in. So the value of the value for every user will be based on the last user to log in and set the value.
I am currently developing a Flask app (have been for the past year) and I'm encountering a rather... Weird bug. I've got a few files that are always included in my Jinja2 templates (navbars), and they use the users' name and avatar. As a consequence, everytime I render a template, I pass it the user. I recently noticed an error on my prod server :
<img alt="image" class="img-circle" src="{{ user.image }}" style="width: 48px;"/>
File "/usr/local/lib/python2.7/dist-packages/jinja2/environment.py", line 397, in getattr
return getattr(obj, attribute)
jinja2.exceptions.UndefinedError: 'user' is undefined
This is in one of my navbars. The method that renders this template uses this :
#mod.route('/broken_pus', methods=['POST', 'GET'])
def view_broken_pus():
return render_template("view_broken_pus.html", user=g.user, urls_for_active_clients=DeletedURLs.objects()[0].urls_for_active_clients, other_urls=DeletedURLs.objects()[0].other_urls)
As you can see, I pass the user=g.user. I do this on every single view of my website. And it works everywhere, EXCEPT on this method, which is pretty small. I have plenty of other routes like that, with just a render template, so I don't get what's the problem.
I also get it on another method, bigger, which always worked before :
#mod.route('/users/add', methods=['GET', 'POST'])
#requires_roles("admin", "project-leader")
def add():
"""
Method adding a new user.
"""
# We do not use WTForms there since we need custom checkboxes for the role
# Instead we use basic HTML and treat the checkboxes here
if request.method == 'POST':
user = User(name=request.form.get('name'),
email=request.form.get('email'))
l = []
# big switch assignement
user.role = l
try:
user.save()
except errors.NotUniqueError:
flash(u'User %s already in database.' % user.name, 'danger')
return redirect(url_for('home'))
flash(u'User %s registered.' % user.name, 'success')
return redirect(url_for('home'))
return render_template('add_user.html', page=url_for('users.add'), user=g.user, clients=Client.objects())
When I first load the form for adding a user, it works. When I add it, for some reason, I get the error (and the user is not saved in the database).
Since this works perfectly on local, I'm starting to suspect a problem on the production server itself. We use nginx and uwsgi for the app, and I recently implemented some Celery tasks. Got any idea ?
Thanks in advance.
Check out flask source for render_template:
It just calls template.render(context), but after the call to before_render_template.send(app, template=template, context=context)
From this, I think there is some before_render_template handler, that modifies context installed.
To debug this down, I may try to call something like this:
from flask import app
#mod.route('/broken_pus', methods=['POST', 'GET'])
def view_broken_pus():
template = app.jinja_env.get_or_select_template("view_broken_pus.html")
return template.render(dict(
user=g.user,
urls_for_active_clients=DeletedURLs.objects()[0].urls_for_active_clients,
other_urls=DeletedURLs.objects()[0].other_urls,
))
If this will work, I will need to dig in who modifies context in before_render_template slot.
I suspect threading. If g is some sort of global reference then you may need to ensure that it is set up on threading.local or that threading locks are used to ensure that no thread can get hold of g.user before some 'other' thread messes with it.
See how do I make a 2.7 python context manager threadsafe for a way to handle 'globals' without sacrificing thread safety.
I'm writing a web app which has a page for admin tasks. One of the tasks is that the admin users must be able to edit other users details. Alas, I've fallen at quite a simple roadblock.
I've set up a very simple jQuery AJAX Get request, successfully transferring a string to the server and back. This is just background, but not the issue. The issue lies in retrieving other user's objects.
At the moment, with a username I know exists, this code which is accessed in views.py, produces a 500 Internal Server Error.
#login_required
def user_edit_getuser(request):
# Like before, get the request's context.
context = RequestContext(request)
inputname = request.GET['inputNameSend']
user_obj = User.objects.get(inputname)
return HttpResponse(inputname) #later will return a JSON String
get takes keyword arguments only: the key is the field to look up.
user_obj = User.objects.get(username=inputname)
Also, you should probably deal with the possibility that the GET request has no inputNameSend key.
For JS development, you can usually see the error page in the Chrome dev tools/Firebug console in the Network tab.
I'm attempting to use Flask and the Flask-Login extension to implement user authentication in a Flask app. The goal is to pull user account information from a database and then log in a user, but I'm getting stuck; however, I've narrowed it down to a particular part of Flask-Login behavior.
According to the Flask-Login documentation, I need to create a user_loader "callback" function. The actual purpose and implementation of this function has had me confused for a few days now:
You will need to provide a user_loader callback. This callback is used
to reload the user object from the user ID stored in the session. It
should take the Unicode ID of a user, and return the corresponding
user object. For example:
#login_manager.user_loader
def load_user(userid):
return User.get(userid)
Now, say I want the user to enter a name and password into a form, check against a database, and log in the user. The database stuff works fine and is no problem for me.
This 'callback' function wants to be passed a user ID #, and return the User object (the contents of which I'm loading from a database). But I don't really get what it's supposed to be checking/doing, since the user IDs are all pulled from the same place anyway. I can 'sort-of' get the callback to work, but it seems messy/hackish and it hits the database with every single resource that the browser requests. I really don't want to check my database in order to download favicon.ico with every page refresh, but flask-login seems like it's forcing this.
If I don't check the database again, then I have no way to return a User object from this function. The User object/class gets created in the flask route for logging in, and is thus out of scope of the callback.
What I can't figure out is how to pass a User object into this callback function, without having to hit the database every single time. Or, otherwise figure out how to go about doing this in a more effective way. I must be missing something fundamental, but I've been staring at it for a few days now, throwing all kinds of functions and methods at it, and nothing is working out.
Here are relevant snippets from my test code. The User class:
class UserClass(UserMixin):
def __init__(self, name, id, active=True):
self.name = name
self.id = id
self.active = active
def is_active(self):
return self.active
The function I made to return the user object to Flask-Login's user_loader callback function:
def check_db(userid):
# query database (again), just so we can pass an object to the callback
db_check = users_collection.find_one({ 'userid' : userid })
UserObject = UserClass(db_check['username'], userid, active=True)
if userObject.id == userid:
return UserObject
else:
return None
The 'callback', which I don't totally understand (must return the User object, which gets created after pulling from database):
#login_manager.user_loader
def load_user(id):
return check_db(id)
The login route:
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST" and "username" in request.form:
username = request.form["username"]
# check MongoDB for the existence of the entered username
db_result = users_collection.find_one({ 'username' : username })
result_id = int(db_result['userid'])
# create User object/instance
User = UserClass(db_result['username'], result_id, active=True)
# if username entered matches database, log user in
if username == db_result['username']:
# log user in,
login_user(User)
return url_for("index"))
else:
flash("Invalid username.")
else:
flash(u"Invalid login.")
return render_template("login.html")
My code 'kinda' works, I can log in and out, but as I said, it must hit the database for absolutely everything, because I have to provide a User object to the callback function in a different namespace/scope from where the rest of the login action takes place. I'm pretty sure I'm doing it all wrong, but I can't figure out how.
The example code provided by flask-login does it this way, but this only works because it's pulling the User objects from a global hard-coded dictionary, not as in a real-world scenario like a database, where the DB must be checked and User objects created after the user enters their login credentials. And I can't seem to find any other example code that illustrates using a database with flask-login.
What am missing here?
You will need to load the user object from the DB upon every request. The strongest reason for that requirement is that Flask-Login will check the authentication token every time to ensure its continuing validity. The calculation of this token may require parameters stored on the user object.
For example, suppose a user has two concurrent sessions. In one of them, the user changes their password. In subsequent requests, the user must be logged out of the second session and forced to login anew for your application to be secure. Think of the case where the second session is stolen because your user forgot to log out of a computer - you want a password change to immediately fix the situation. You might also want to give your admins the ability to kick a user out.
For such forced logout to happen, the authentication token stored in a cookie must 1) be based in part on the password or something else that changes each time a new password is set; 2) be checked before running any view, against the latest known attributes of the user object - which are stored in the DB.
I do share your concerns Edmond: hitting database each time when one needs to know user's role or name is insane. Best way would be to store your User object in session or even application-wide cache which gets updated from the DB each couple of minutes. I personally use Redis for that (that way website can be run by multiple threads/processes while using single cache entry point). I just make sure Redis is configured with password and non-default port, and any confidential data (like user hashes etc) are stored there in an encrypted form. Cache can be populated by a separate script running on specified interval, or separate thread can be spawned in Flask. Note: Flask-Session can be also configured to use (the same) redis instance to store session data, in that case instance with 'bytes' datatype will be needed, for a regular cache you might often go with instance type which automatically translates bytes into strings (decode_responses=True).
Here is my code, another User as data mapping object provide query_pwd_md5 method.
User login:
#app.route('/users/login', methods=['POST'])
def login():
# check post.
uname = request.form.get('user_name')
request_pwd = request.form.get('password_md5')
user = User()
user.id = uname
try:
user.check_pwd(request_pwd, BacktestUser.query_pwd_md5(
uname, DBSessionMaker.get_session()
))
if user.is_authenticated:
login_user(user)
LOGGER.info('User login, username: {}'.format(user.id))
return utils.serialize({'userName': uname}, msg='login success.')
LOGGER.info('User login failed, username: {}'.format(user.id))
return abort(401)
except (MultipleResultsFound, TypeError):
return abort(401)
User class:
class User(UserMixin):
"""Flask-login user class.
"""
def __init__(self):
self.id = None
self._is_authenticated = False
self._is_active = True
self._is_anoymous = False
#property
def is_authenticated(self):
return self._is_authenticated
#is_authenticated.setter
def is_authenticated(self, val):
self._is_authenticated = val
#property
def is_active(self):
return self._is_active
#is_active.setter
def is_active(self, val):
self._is_active = val
#property
def is_anoymous(self):
return self._is_anoymous
#is_anoymous.setter
def is_anoymous(self, val):
self._is_anoymous = val
def check_pwd(self, request_pwd, pwd):
"""Check user request pwd and update authenticate status.
Args:
request_pwd: (str)
pwd: (unicode)
"""
if request_pwd:
self.is_authenticated = request_pwd == str(pwd)
else:
self.is_authenticated = False