I am getting RuntimeError: working outside of request context error while running a test in Flask. I've tried multiple suggestions from other threads, but none has worked for me.
Part of my views.py:
#user_app.route('/login', methods =('GET', 'POST'))
def login():
form = LoginForm()
error = None
if form.validate_on_submit():
user = User.objects.filter(username=form.username.data).first()
if user:
if bc.hashpw(form.password.data, user.password) == user.password:
session['username']=form.username.data
return 'User Logged In'
else:
user = None
if not user:
error = 'Incorrect credentials'
return render_template('user/login.html',form=form,error=error)
Relevant part of my tests.py:
from application import create_app as create_app_base
def create_app(self):
self.db_name = 'flaskbook_test'
return create_app_base(
MONGODB_SETTINGS={'DB':self.db_name},
TESTING=True,
WTF_CSRF_ENABLED=False,
SECRET_KEY='SecretKey'
)
def setUp(self):
self.app_factory = self.create_app()
self.app = self.app_factory.test_client()
#self.app.application.app_context().push() <-- this did not help
def tearDown(self):
db = _get_db()
db.client.drop_database(db)
def test_login_user(self):
#create user
self.app.post('/register', data=self.user_dict())
#login user
rv = self.app.post('/login',data=dict(
username='username',
password='password'
))
#check session is set
with self.app as c:
rv = c.get('/')
assert session.get('username') == self.user_dict()['username']
I have already tried adding app_context and self.app.application.app_context().push() as mentioned above:
with self.app.application.app_context():
assert session.get('username') == self.user_dict()['username']
But it didn't work. Whenever I call session['username'] I get RuntimeError: working outside of request context.
My requirements.txt: Flask0.10.1 Flask-Script 2.0.5 flask-mongoengine 0.7.4
Please help.
What you want is the request context, not the app context.
Flask includes some handy functions to push a request context for you - check out the Flask testing docs and you'll see a lot of relevant info, including the test_request_context method on the app object.
Combine that with app.test_client to push a request context and then simulate client behaviour such as POSTing to your endpoint. Try this:
with self.app.test_request_context('/'), self.app.test_client() as c:
rv = c.post('/')
assert session.get('username') == self.user_dict()['username']
Related
Looking at the Flask-Login docs and several relatively old threads on Stackoverflow, I am concerned about the security of my solution for passing the restricted page URL through the login process.
First, I was getting Attribution Error when trying to use #login_manager.unauthorized_handler. ("login_manager does not have an attribute unauthorized_handler.") This a separate issue altogether, because it really should have worked. (See app factory below.)
When I applied a redirect_destination function without the modified #login_manager.unauthorized_handler, the login failed to redirect to the target destination.
def redirect_destination(dest_url, fallback):
try:
dest_url = url_for(dest_url)
except:
return redirect(fallback)
return redirect(dest_url)
Then I applied a session instance to be passed from the destination URL through login along with the fresh_login_required decorator and .is_authenticated property instead of the standard login_required.
target blueprint:
#target.route('/target', methods=['GET', 'POST'])
#fresh_login_required
def target():
if current_user.is_authenticated:
...
return render_template('target.html')
else:
session['dest_url']=request.endpoint
return redirect(url_for('account.login'))
auth blueprint:
#account.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
dest_url = session.get('dest_url')
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if not user:
return redirect(url_for('account.login'))
if user.check_password(form.password.data):
login_user(user, remember=form.remember_me.data)
...
return redirect_destination(dest_url, fallback=url_for('main.index'))
else:
return redirect(url_for('account.login'))
return render_template('account/login.html')
app factory:
...
login_manager = LoginManager()
login_manager.login_view = 'account.login' #hard-coded view
login_manager.refresh_view = 'account.refresh' #hard-coded view
def create_app():
app = Flask(__name__,
static_url_path='/',
static_folder='../app/static',
template_folder='../app/templates')
db.init_app(app)
login_manager.init_app(app)
from app import models
from .templates.account import account
from .templates.main import main
from .templates.target import target
app.register_blueprint(main)
app.register_blueprint(account)
app.register_blueprint(target)
return app
So, this solution works, and it can be applied to multiple blueprints. My concern is that I am missing some important details that necessitated other, more complex solutions. Is there a security weak point? Other issues?
References:
https://flask-login.readthedocs.io/en/latest/
How do I pass through the "next" URL with Flask and Flask-login?
Flask/Werkzeug, how to return previous page after login
Flask how to redirect to previous page after successful login
Get next URL using flask redirect
Flask Basic HTTP Auth use login page
Following are the code files with relevant code snippets:
init.py:
app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
app.config['SECRET_KEY'] = 'super-secret'
In auth.py:
def authenticate_user(login, password):
'''
Return dict or None after checking against db for valid user
'''
s = select([users]).where(users.c.email==login)
result_set = conn.execute(s)
if result_set.rowcount == 1:
for r in result_set:
print r[users.c.password], 'result_set[users.c.password]'
if pwd_context.verify(password, r[users.c.password]):
# There is only one unique email/password pair
print 'matched'
return dict(r)
else:
return None
return
How to get the access_token value for the user on login? I have installed Flassk-JWT in the virtualenv and followed this doc: https://pythonhosted.org/Flask-JWT/ But please note I am not using OOPs ie. User class etc. I am using sqlalchemy core with Flask and python. To further use this token, I need to call it as a decorator for the API call is what I understand as:
#app.route('/rt/api/v1.0/list', methods=['GET'])
#jwt_required()
In views.py:
from myapp.auth import authenticate_user
#app.route('/auth', methods=['POST','GET'])
def login():
email = request.form["email"]
password = request.form["password"]
if request.method == 'POST':
result_set = authenticate_user(email, password)
if result_set:
session['email'] = result_set['email']
user_dict = result_set
if user_dict:
session['email'] = user_dict['email']
jwt = JWT(app, user_dict['email'], user_dict["id"])
How to exactly connect the various code files to get the access token value is what I am stuck up with.Please guide. Also Wish to exclude the login API request from the before_request callback(). All other APIs can have the before and after_request callbacks() executed.
Finally found a way better implementation with the basic usage on readthedocs
I want to build a simple webapp as part of my learning activity. Webapp is supposed to ask for user to input their email_id if it encounters a first time visitor else it remembers the user through cookie and automatically logs him/her in to carry out the functions.
This is my first time with creating a user based web app. I have a blue print in my mind but I am unable to figure out how to implement it. Primarily I am confused with respect to the way of collecting user cookie. I have looked into various tutorials and flask_login but I think what I want to implement is much simpler as compared to what flask_login is implementing.
I also tried using flask.session but it was a bit difficult to understand and I ended up with a flawed implementation.
Here is what I have so far (it is rudimentary and meant to communicate my use case):
from flask import render_template, request, redirect, url_for
#app.route("/", methods= ["GET"])
def first_page():
cookie = response.headers['cookie']
if database.lookup(cookie):
user = database.get(cookie) # it returns user_email related to that cookie id
else:
return redirect_url(url_for('login'))
data = generateSomeData() # some function
return redirect(url_for('do_that'), user_id, data, stats)
#app.route('/do_that', methods =['GET'])
def do_that(user_id):
return render_template('interface.html', user_id, stats,data) # it uses Jinja template
#app.route('/submit', methods =["GET"])
def submit():
# i want to get all the information here
user_id = request.form['user_id']# some data
answer = request.form['answer'] # some response to be recorded
data = request.form['data'] # same data that I passed in do_that to keep
database.update(data,answer,user_id)
return redirect(url_for('/do_that'))
#app.route('/login', methods=['GET'])
def login():
return render_template('login.html')
#app.route('/loggedIn', methods =['GET'])
def loggedIn():
cookie = response.headers['cookie']
user_email = response.form['user_email']
database.insert(cookie, user_email)
return redirect(url_for('first_page'))
You can access request cookies through the request.cookies dictionary and set cookies by using either make_response or just storing the result of calling render_template in a variable and then calling set_cookie on the response object:
#app.route("/")
def home():
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return render_template('welcome.html', user=user)
else:
return redirect(url_for('login'))
else:
return redirect(url_for('login'))
#app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
# You should really validate that these fields
# are provided, rather than displaying an ugly
# error message, but for the sake of a simple
# example we'll just assume they are provided
user_name = request.form["name"]
password = request.form["password"]
user = db.find_by_name_and_password(user_name, password)
if not user:
# Again, throwing an error is not a user-friendly
# way of handling this, but this is just an example
raise ValueError("Invalid username or password supplied")
# Note we don't *return* the response immediately
response = redirect(url_for("do_that"))
response.set_cookie('YourSessionCookie', user.id)
return response
#app.route("/do-that")
def do_that():
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return render_template('do_that.html', user=user)
else:
return redirect(url_for('login'))
else:
return redirect(url_for('login'))
DRYing up the code
Now, you'll note there is a lot of boilerplate in the home and do_that methods, all related to login. You can avoid that by writing your own decorator (see What is a decorator if you want to learn more about them):
from functools import wraps
from flask import flash
def login_required(function_to_protect):
#wraps(function_to_protect)
def wrapper(*args, **kwargs):
user_id = request.cookies.get('YourSessionCookie')
if user_id:
user = database.get(user_id)
if user:
# Success!
return function_to_protect(*args, **kwargs)
else:
flash("Session exists, but user does not exist (anymore)")
return redirect(url_for('login'))
else:
flash("Please log in")
return redirect(url_for('login'))
return wrapper
Then your home and do_that methods get much shorter:
# Note that login_required needs to come before app.route
# Because decorators are applied from closest to furthest
# and we don't want to route and then check login status
#app.route("/")
#login_required
def home():
# For bonus points we *could* store the user
# in a thread-local so we don't have to hit
# the database again (and we get rid of *this* boilerplate too).
user = database.get(request.cookies['YourSessionCookie'])
return render_template('welcome.html', user=user)
#app.route("/do-that")
#login_required
def do_that():
user = database.get(request.cookies['YourSessionCookie'])
return render_template('welcome.html', user=user)
Using what's provided
If you don't need your cookie to have a particular name, I would recommend using flask.session as it already has a lot of niceties built into it (it's signed so it can't be tampered with, can be set to be HTTP only, etc.). That DRYs up our login_required decorator even more:
# You have to set the secret key for sessions to work
# Make sure you keep this secret
app.secret_key = 'something simple for now'
from flask import flash, session
def login_required(function_to_protect):
#wraps(function_to_protect)
def wrapper(*args, **kwargs):
user_id = session.get('user_id')
if user_id:
user = database.get(user_id)
if user:
# Success!
return function_to_protect(*args, **kwargs)
else:
flash("Session exists, but user does not exist (anymore)")
return redirect(url_for('login'))
else:
flash("Please log in")
return redirect(url_for('login'))
And then your individual methods can get the user via:
user = database.get(session['user_id'])
I have built a website using flask (www.csppdb.com). Sometimes when I log in as one user, log out, then login as another user I still see pages from the first user I logged in as. This problem is immediately fixed when the page is refreshed. I think this is called "caching" if I am not mistaken. Is there any way I could disable this on a site wide level so that every page that is visited needs a new refresh?
It would be like sharing your computer with a friend. He logs into Facebook, then logs out. Now you log in on his computer and you see his profile... (awkward). After you refresh the page the problem is fixed.
Here is some of my code. I was using flask-login but I then tried to "roll my own"
from flask.ext.mysql import MySQL
import os
from flask import Flask, request, jsonify, session, url_for, redirect, \
render_template, g, flash
from data import *
from werkzeug import check_password_hash, generate_password_hash
import config
app = Flask(__name__)
mysql = MySQL()
app.config['MYSQL_DATABASE_HOST'] = os.environ['MYSQL_DATABASE_HOST'] if 'MYSQL_DATABASE_HOST' in os.environ else config.MYSQL_DATABASE_HOST
app.config['MYSQL_DATABASE_PORT'] = os.environ['MYSQL_DATABASE_PORT'] if 'MYSQL_DATABASE_PORT' in os.environ else config.MYSQL_DATABASE_PORT
app.config['MYSQL_DATABASE_USER'] = os.environ['MYSQL_DATABASE_USER'] if 'MYSQL_DATABASE_USER' in os.environ else config.MYSQL_DATABASE_USER
app.config['MYSQL_DATABASE_PASSWORD'] = os.environ['MYSQL_DATABASE_PASSWORD'] if 'MYSQL_DATABASE_PASSWORD' in os.environ else config.MYSQL_DATABASE_PASSWORD
app.config['MYSQL_DATABASE_DB'] = os.environ['MYSQL_DATABASE_DB'] if 'MYSQL_DATABASE_DB' in os.environ else config.MYSQL_DATABASE_DB
mysql.init_app(app)
if 'SECRET_KEY' in os.environ: app.config['SECRET_KEY'] = os.environ['SECRET_KEY']
else: app.config['SECRET_KEY'] = os.urandom(24)
def connect_db(): return mysql.connect()
def check_auth():
g.user = None
if 'username' in session:
g.user = get_user(session['username'])
return
return redirect(url_for('login'))
#app.route('/')
def home():
if 'username' in session: return redirect(url_for('main'))
return render_template('home.html')
def connect_db(): return mysql.connect()
#app.teardown_request
def teardown_request(exception):
if exception: print exception
g.db.close()
#app.before_request
def before_request():
print session.keys(), session.values()
print("before request")
print ('username' in session, "in session?")
g.db = connect_db()
g.user = None
if "username" in session:
g.user = get_user(session['username'])
#app.route('/login/', methods=['GET', 'POST'])
def login():
"""Logs the user in."""
if 'username' in session:
return redirect(url_for('main'))
error = None
if request.method == 'POST':
print("login hit")
user = get_user(request.form['username'])
if user is None:
error = 'Invalid username'
print error
elif not check_password_hash(user.password, request.form['password']):
error = 'Invalid password'
print error
else:
flash('You were logged in')
print "logged in"
session['username'] = request.form['username']
g.user = request.form['username']
print error, "error"
return redirect(url_for('main'))
return render_template('login.html', error=error)
Setting the cache to be max-age=0 fixed it.
#app.after_request
def add_header(response):
"""
Add headers to both force latest IE rendering engine or Chrome Frame,
and also to cache the rendered page for 10 minutes.
"""
response.headers['X-UA-Compatible'] = 'IE=Edge,chrome=1'
response.headers['Cache-Control'] = 'public, max-age=0'
return response
To stop browser caching on these sort of pages you need to set some HTTP response headers.
Cache-Control: no-cache, no-store
Pragma: no-cache
Once you do this then the browser wont cache those pages. I dont know how to do this with "flask" so I will leave that as an exercise for you :)
This question shows how to add a response header Flask/Werkzeug how to attach HTTP content-length header to file download
I have a flask application that has a view function that looks like this:
#login_required
#app.route('/suspect_tracker/new_list', methods=['GET', 'POST'])
def new_list():
form = ListForm()
if form.validate_on_submit():
if form.private:
privacy_setting = 1
else:
privacy_setting = 0
new_list = List(name=form.name.data, last_updated=datetime.utcnow(), user_id=g.user.id, private=privacy_setting)
db.session.add(new_list)
db.session.commit()
flash('New list %s added' % new_list.name)
return redirect('/suspect_tracker/' + form.name.data)
else:
flash(form.name.errors)
return render_template('newlist.html', title="Make a new list!", form=form)
And I am attempting to write a test for it, with the test looking like this:
from config import basedir
from app import app, db
from app.models import User, List, Suspect, SuspectList
from flask import g
from flask_testing import TestCase
class TestViews(TestCase):
def create_app(self):
app.config['TESTING'] = True
app.config['WTF_CSRF_ENABLED'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'test.db')
self.client = app.test_client()
return app
def setUp(self):
self.app = self.create_app()
db.create_all()
test_user = User(nickname="test")
db.session.add(test_user)
db.session.commit()
g.user = User.query.filter_by(id=1).first()
def tearDown(self):
db.session.remove()
db.drop_all()
def test_new_list_view(self):
self.client.get('/suspect_tracker/new_list/')
form = {'name':'Test', 'private':False}
g.user = User.query.filter_by(id=1).first()
self.client.post('/suspect_tracker/new_list', data=form)
assert List.query.filter_by(name="Test").first() != None
After running the test, the assertion fails, and I have tested to see that after running self.client.post, List does not contain a new list, and is still empty. This leads me to believe that form.validate_on_submit() returned false, which leads me to believe that I am not passing the correct data in the self.client.post() function in test_new_list_view(). My question is, how do I correctly create a ListForm() in the testing function and then POST it with self.client.post() to test the new_list() function?
I've fixed the issue with getting the form data to properly be sent, so now the form validates but I have no idea how to set g.user to be the mock user so when I try running the test I get an "AttributeError: 'AnonymousUserMixin' object has no attribute 'id'" error.