I am developing a web application that needs to login to the database with credentials that are provided by the end user; the application itself does not have a login to the database.
The problem is how to create one connection per user session.
One approach is:
Request user's credentials
Check if credentials are valid against db backend
Store credentials in session-level variable
Problem with this approach is, on each subsequent request for that session; you would need to create a new connection; and this will quickly exhaust the max connections to the server.
I am using Flask with Oracle.
In Flask, there is a g object, which stores request-scoped objects. This snippet though, does not work:
app = Flask(__name__)
app.config.from_object(__name__)
def login_required(f):
#wraps(f)
def decorated_function(*args, **kwargs):
if g.db is None:
return redirect(url_for('login', next=request.url))
return f(*args, **kwargs)
return decorated_function
class LoginForm(Form):
username = TextField('Username', [validators.Length(min=4, max=25)])
password = PasswordField('Password', [validators.Required()])
#app.route('/app', methods=['GET','POST'])
#login_required
def index():
return 'Index'
#app.route('/', methods=['GET','POST'])
def login():
form = LoginForm(request.form)
if request.method == 'POST':
if form.validate():
try:
dsn = cx_Oracle.makedsn(app.config['DB_HOST'],
app.config['DB_PORT'], app.config['DB_SID'])
g.db = cx_Oracle.connect(form.username.data,
form.password.data, dsn)
except cx_Oracle.DatabaseError as e:
flash(unicode(e), 'error')
return render_template('login.html', form=form)
return redirect(url_for('index'))
else:
return render_template('login.html', form=form)
else:
return render_template('login.html', form=form)
AttributeError: '_RequestGlobals' object has no attribute 'db'
The reason why this snippet is not working is this line
if g.db is None:
You are accessing an attribute which does not belong to g. Add these lines of code:
#app.before_request
def before_request():
g.db = None
Functions marked with before_request() are called before a request and passed no arguments.
Regarding Connection Pooling
There is an ORM and SQL Toolkit called SQLAlchemy in Python which does connection pooling automatically for you.
It also manages g object of flask and creates prepared SQL statements which are much more secure.
It has two parts:
1. Core which is a SQL abstraction toolkit.
2. ORM is an optional package which builds on top of Core.
So if you don't want to use ORM then simply use its Core with all the features intact.
Check here.
It supports Oracle via cx_Oracle and it can be set up as mentioned in SQLAlchemy Docs here.
Check this question for some more discussion on connection pooling in python.
Sounds like you need to implement connection pooling. Instead of requesting a new connection for each request (which does not scale at all), you simply check out a connection from the pool with the required attributes. If no suitable connection is available, the pool would create a new one. The pool would also need to handle the opposite case of course in closing connections that haven't been used for some time.
You might want to check if such a pool is available for python. For java oracle supports this with UCP and for OCI with the session pool. If I'm not mistaken a connection pool is even provided for .Net.
Related
I have created a python webapp with Flask and it seems like I am having connection issues with the database. I think this is because I don't close my sessions somewhere in my code.
I have
db = SQLAlchemy(app)
for the database and use
#views.route('/test/', methods=['GET', 'POST'])
def test():
db.session.add(something)
db.session.commit()
#views.route('/another_page/', methods=['GET', 'POST'])
def page():
some_records = User.query.get(some_ids)
for adding records to the database.
When do I have to close my session in this case? Is there a way to close the connection after the user leaves? Should I close every time a page is done with the database? Do I need to close my connection after a query?
The documentation says next:
As in the declarative approach, you need to close the session after each request or application context shutdown. Put this into your application module:
from yourapplication.database import db_session
#app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove()
UPD: In case of Flask-SQLAlchemy this staff is hardcoded, thus you don't need to care about it when develop.
I'm having trouble with understanding how current_user from Flask-Login library works. For example when two users access the same route at the same time and withing its function I call multiple modules that also use the current_user as an import. I will elaborate more with some code:
I have this route called update_account(I removed some parts because they are not related to my question):
#users.route('/account/user/<username>/update', methods=['GET', 'POST'])
def update_account(username):
update_account_form = UpdateForm()
if update_account_form.validate_on_submit():
#here we handle updating from another module
if AccountManager.update_account(update_account_form): #retuns True if no errors has occured
flash('Your account has been successfully updated', "success")
return redirect(url_for('users.update_account', username=current_user.username))
flash('Your client matched max requests', "warning")
return redirect(url_for('users.update_account', username=current_user.username))
return render_template('account/update.html', update_form=update_account_form)
My question is about the part I call AccountManager.update_account(update_account_form) because im not passing any of current_users data instead i'm importing current_user in that module as well and thats how I get the data. Below is how I implemented that:
from flask_login import login_user, current_user
class AccountManager:
#staticmethod
def update_account(account_form):
if current_user.request_counter >= 5:
return False
current_user.username = account_form.username.data.strip()
current_user.email = account_form.email.data.strip()
if account_form.change_password.data:
current_user.password = bcrypt.generate_password_hash(account_form.password.data).decode('utf-8')
db.session.commit()
return True
My question is right here. Is this safe? Should I pass current_user as a parameter instead of importing it here? because maybe if another request comes the current_user changes and this method will change someone else data.
thanks for your time.
What you are doing is fine.
current_user is dependent on the request context so you will not get a different user just because another user's request came in before you finished processing the first.
Right now, my webapp has a full auth system implemented through Flask-Login. But say that I have this controller:
#app.route("/search_query")
#login_required
def search_query():
query = request.args.get("query")
return jsonify(search(query))
This works great when you login to the page and make searches against it, if you're authenticated it goes through, otherwise it routes you back to the login page (through an unauthorized_handler function).
#lm.unauthorized_handler
def unauthorized():
return redirect(url_for("login"))
The problem is, I want to be able to use some form of simple HTTP authentication for this without having two separate auth systems so that I can make REST API calls against it. In short, I want to avoid having to use both Flask-Login and HTTPBasicAuth, and just use Flask-Login to achieve simple authentication, is this possible through Flask-Login?
Use the flask-login's request_loader to load from request auth header. You don't need to implement the decorator yourself, and current_user will point to the user loaded.
Example:
login_manager = LoginManager()
#login_manager.request_loader
def load_user_from_header():
auth = request.authorization
if not auth:
return None
user = User.verify_auth(auth.username, auth.password):
if not user:
abort(401)
return user
With that, you can mark a view as login required by:
#app.route('/user/info')
#login_required
def user_info():
return render_template('user_info.html', current_user.name)
If you looking for users to authenticate and make REST API calls they are going to have to send their credentials with every api call.
What you could do is a new decorator that checks for the auth header and authenticate the user.
Please refer to the entire app that I've posted on codeshare for more info:
http://codeshare.io/bByyF
Example using basic auth:
def load_user_from_request(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if current_user.is_authenticated:
# allow user through
return f(*args, **kwargs)
if auth and check_auth(auth.username, auth.password):
return f(*args, **kwargs)
else:
return bad_auth()
return decorated
With that code you can now decorate your views to check for the header.
#app.route("/search_query")
#load_user_from_request
def search_query():
query = request.args.get("query")
return jsonify(search(query))
you can curl my example app like this:
curl --user admin#admin.com:secret http://0.0.0.05000/success
I have a flask app where I have to delete and update user information, like this (simplified):
#app.route('/<user>')
def show_user(user):
""" Show user information """
render_template('show_user.html')
#app.route('/delete/<user>')
def delete_user(user):
""" Delete user from database """
delete_user_from_db(user)
return redirect(url_for('show_users', user=user)
#app.route('/update/<user>', method=["POST"])
def update_user(user):
""" Update user information """
update(user, stuff_from_POST)
return redirect(url_for('show_users', user=user)
For each of these methods I need to verify whether the user specified in the URL is really a valid user, so I'd do something like this at the beginning of all those functions:
if user not in mydb:
do(something)
abort(404)
This is rather cumbersome, and since I will be having more functions that depend on the user to be valid, I was wondering if it were possible to wrap that block in another function that gets automatically executed when those routes are called.
Thanks in advance.
Use the before_request hook function see docs
#app.before_request
def before_request():
if user not in mydb:
do(something)
abort(404)
Edit:
I tried this
from flask import Flask, request
app = Flask(__name__)
db = ['paul', 'steve', 'anna']
#app.before_request
def before_request():
if request.endpoint in ['show_user', 'delete_user', 'update_user']:
user = request.path[request.path.rfind('/') + 1:]
if user not in db:
return 'user not found', 404
#app.route('/<user>')
def show_user(user):
""" Show user information """
return 'hello %s' % user
#app.route('/other')
def show_other():
""" Demonstrates other routes are not effected by before_request """
return 'other stuff'
if __name__ == "__main__":
app.run()
It's not actually as neat as I hoped but it works.
It's a little unfortunate to have to maintain which endpoints do what in the before_request function. If you were doing REST you might be able to merge these endpoints into one and simply use different http methods for each action.
I'm developing a Flask application and using Flask-security for user authentication (which in turn uses Flask-login underneath).
I have a route which requires authentication, /user. I'm trying to write a unit test which tests that, for an authenticated user, this returns the appropriate response.
In my unittest I'm creating a user and logging as that user like so:
from unittest import TestCase
from app import app, db
from models import User
from flask_security.utils import login_user
class UserTest(TestCase):
def setUp(self):
self.app = app
self.client = self.app.test_client()
self._ctx = self.app.test_request_context()
self._ctx.push()
db.create_all()
def tearDown(self):
if self._ctx is not None:
self._ctx.pop()
db.session.remove()
db.drop_all()
def test_user_authentication():
# (the test case is within a test request context)
user = User(active=True)
db.session.add(user)
db.session.commit()
login_user(user)
# current_user here is the user
print(current_user)
# current_user within this request is an anonymous user
r = test_client.get('/user')
Within the test current_user returns the correct user. However, the requested view always returns an AnonymousUser as the current_user.
The /user route is defined as:
class CurrentUser(Resource):
def get(self):
return current_user # returns an AnonymousUser
I'm fairly certain I'm just not fully understanding how testing Flask request contexts work. I've read this Flask Request Context documentation a bunch but am still not understanding how to approach this particular unit test.
The problem is different request contexts.
In your normal Flask application, each request creates a new context which will be reused through the whole chain until creating the final response and sending it back to the browser.
When you create and run Flask tests and execute a request (e.g. self.client.post(...)) the context is discarded after receiving the response. Therefore, the current_user is always an AnonymousUser.
To fix this, we have to tell Flask to reuse the same context for the whole test. You can do that by simply wrapping your code with:
with self.client:
You can read more about this topic in the following wonderful article:
https://realpython.com/blog/python/python-web-applications-with-flask-part-iii/
Example
Before:
def test_that_something_works():
response = self.client.post('login', { username: 'James', password: '007' })
# this will fail, because current_user is an AnonymousUser
assertEquals(current_user.username, 'James')
After:
def test_that_something_works():
with self.client:
response = self.client.post('login', { username: 'James', password: '007' })
# success
assertEquals(current_user.username, 'James')
The problem is that the test_client.get() call causes a new request context to be pushed, so the one you pushed in your the setUp() method of your test case is not the one that the /user handler sees.
I think the approach shown in the Logging In and Out and Test Adding Messages sections of the documentation is the best approach for testing logins. The idea is to send the login request through the application, like a regular client would. This will take care of registering the logged in user in the user session of the test client.
I didn't much like the other solution shown, mainly because you have to keep your password in a unit test file (and I'm using Flask-LDAP-Login, so it's nonobvious to add a dummy user, etc.), so I hacked around it:
In the place where I set up my test app, I added:
#app.route('/auto_login')
def auto_login():
user = ( models.User
.query
.filter_by(username="Test User")
.first() )
login_user(user, remember=True)
return "ok"
However, I am making quite a lot of changes to the test instance of the flask app, like using a different DB, where I construct it, so adding a route doesn't make the code noticeably messier. Obv this route doesn't exist in the real app.
Then I do:
def login(self):
response = self.app.test_client.get("/auto_login")
Anything done after that with test_client should be logged in.
From the docs: https://flask-login.readthedocs.io/en/latest/
It can be convenient to globally turn off authentication when unit testing. To enable this, if the application configuration variable LOGIN_DISABLED is set to True, this decorator will be ignored.