Flask View Testing General and with Subdomains - python

I'm working on testing the views that I have for a Flask application and I'm having some issues. I use a csrf token for my form submissions and I also use subdomains for logging in and I was wondering how to test for that as well. If anyone has any experience or could give me an example or some direction, I would appreciate it because I've already gone through all of the guides and documentation I could fine.
Below is what I'm trying to test and the test I'm running.
#app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
try:
#acts as redirect, but with subdomain
return redirect_subdomain('login', request.url, request.form['subdomain'])
except OrganizationDoesNotExistException:
return render_template('login.html', subdomain=False)
return render_template('login.html', subdomain=False)
These are the tests I'm running just to get started. It only accepts a subdomain (organization) that will be used to redirect the user to the subdomain specific login, which works, I just want to know how to write tests for the future:
def test_login(self):
rv = self.login('test')
print(rv.data.decode('utf-8'))
pass
def login(self, organization):
return self.app.post('/login', data=dict(
organization=organization
), follow_redirects=True)
I know these are basic tests and don't really test much, but I still get errors and I want to know how to go about testing views in general and when there are subdomains involved, like in my case.

In order to be able to create unittests for a flask app using subdomain routing, the test_client() function can be used. For this to work, you will need to set the allow_subdomain_redirects attribute on the test_client object to True.
Here is an example test case for a flask app using subdomain routing.
import yourapp
import unittest
class FlaskTestCase(unittest.TestCase):
def setUp(self):
self.app = yourapp.app.test_client()
self.app.allow_subdomain_redirects = True
def test_request(self):
r = self.app.get('http://subdomain.domain.com/route/', query_string='key=value')
self.assertEquals(r.data, known_result)

Related

Injecting a Flask Request into another Flask App

Is there a way to inject a Flask request object into a different Flask app. This is what I'm trying to do:
app = flask.Flask(__name__)
#app.route('/foo/<id>')
def do_something(id):
return _process_request(id)
def say_hello(request):
# request is an instance of flask.Request.
# I want to inject it into 'app'
I'm trying this with Google Cloud Functions, where say_hello() is a function that is invoked by the cloud runtime. It receives a flask.Request as the argument, which I want to then process through my own set of routes.
I tried the following, which doesn't work:
def say_hello(request):
with app.request_context(request.environ):
return app.full_dispatch_request()
This responds with 404 errors for all requests.
Edit:
The simple way to implement say_hello() is as follows:
def say_hello(request):
if request.method == 'GET' and request.path.startswith('/foo/'):
return do_something(_get_id(request.path))
flask.abort(404)
This essentially requires me to write the route matching logic myself. I'm wondering if there's a way to avoid doing that, and instead use Flask's built-in decorators and routing capabilities.
Edit 2:
Interestingly, dispatching across apps work locally:
app = flask.Flask(__name__)
# Add app.routes here
functions = flask.Flask('functions')
#functions.route('/', defaults={'path': ''})
#functions.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
def catch_all(path):
with app.request_context(flask.request.environ):
return app.full_dispatch_request()
if __name__ == '__main__':
functions.run()
But the same technique doesn't seem to work on GCF.
I wouldn't recommend this method, but this is technically possible by abusing the request stack and rewriting the current request and re-dispatching it.
However, you'll still need to do some type of custom "routing" to properly set the url_rule, as the incoming request from GCF won't have it (unless you explicitly provide it via the request):
from flask import Flask, _request_ctx_stack
from werkzeug.routing import Rule
app = Flask(__name__)
#app.route('/hi')
def hi(*args, **kwargs):
return 'Hi!'
def say_hello(request):
ctx = _request_ctx_stack.top
request = ctx.request
request.url_rule = Rule('/hi', endpoint='hi')
ctx.request = request
_request_ctx_stack.push(ctx)
return app.dispatch_request()

login_required decorator not redirecting to correct endpoint

Using Flask-security extension, I was trying to protect some views, for example:
from flask_security import login_required
#auth.route('/signin', methods=['GET', 'POST'])
def signin():
#...
#...
#auth.route('/change-password', methods=['GET', 'POST'])
#login_required
def change_password():
#...
With the following config.py for Flask-Security:
SECURITY_URL_PREFIX = '/auth'
SECURITY_LOGOUT_URL = '/signout'
SECURITY_UNAUTHORIZED_VIEW = '/signin'
And yet, when I try to access a protected view, I get the following werkzeug.routing.BuildError message:
werkzeug.routing.BuildError: Could not build url for endpoint 'auth.login'.
Did you mean 'auth.signin' instead?
What could be the reason behind this error?
Flask-security uses Flask-login's login_required decorator and as per documentation:
flask_login.login_required(func)
If you decorate a view with this, it will ensure that the current
user is logged in and authenticated before calling the actual view.
(If they are not, it calls the LoginManager.unauthorized callback.)
While one can use LoginManager.unauthorized callback to setup a response:
unauthorized_handler(callback)
This will set the callback for the unauthorized method, which among
other things is used by login_required. It takes no arguments,
and should return a response to be sent to the user instead of their
normal view.
So, I ended up defining the callback myself:
#app.login_manager.unauthorized_handler
def unauth_handler():
if request.is_xhr:
return jsonify(success=False,
data={'login_required': True},
message='Authorize please to access this page.'), 401
else:
return redirect(url_for('auth.signin'))

How to override a Flask blueprint URL?

I am using flask-mwoauth to create a simple application in Flask using OAuth authentication on Mediawiki (and Wikipedia in particular).
flask-mwoauth is a blueprint that provides some convenience methods to interact with Mediawiki Extensions:OAuth and adds the following URIs:
/login - runs the OAuth handshake and returns the user to /
/login?next=/someurl will return the user to /someurl
/logout - clears the users' access tokens
/logout?next=/someurl will return the user to /someurl
/oauth-callback - callback from MW to finish the handshake
The users' OAuth key and secret are stored in the session.
I would like to be able to create custom responses for some of this custom URIs. Take for example /logout, the definition of the response in very simple (__init__.py#L56):
#self.bp.route('/logout')
def logout():
session['mwo_token'] = None
session['username'] = None
if 'next' in request.args:
return redirect(request.args['next'])
return "Logged out!"
I would like to define in my application the route /logout with a custom response (for example, rendering a template), however if I use the blueprint then the route #app.route("/logout") is ignored.
What I would like to know if it is possible to "extend" the blueprint in the sense that I can define a route /logout in my app, call the original method from the blueprint and then serve a customized response.
If you want to completely redefine behavior of route the best way is override MWOAuth class. Here an example which works:
import os
from flask import Flask, Blueprint
from flask_mwoauth import MWOAuth
app = Flask(__name__)
app.secret_key = os.urandom(24)
class MyMWOAuth(MWOAuth):
def __init__(self,
base_url='https://www.mediawiki.org/w',
clean_url="Deprecated",
default_return_to='index',
consumer_key=None,
consumer_secret=None,
name="Deprecated"):
# I skipped other rows. It's just an example
self.bp = Blueprint('mwoauth', __name__)
# By the way you can customize here login and oauth-callback
#self.bp.route('/logout')
def logout():
# your custom logic here...
return "My custom logout"
mwoauth = MyMWOAuth(consumer_key='test', consumer_secret='test')
app.register_blueprint(mwoauth.bp)
if __name__ == "__main__":
app.run(debug=True, threaded=True)
Let's open /logout. You will see My custom logout.
As you can see registration of BluePrint routes takes place in init method of MWOAuth.
The second way is to use request callbacks. Here an example which demonstrates the change in the body of the response after logout.
from flask import g, request
def after_this_request(f):
if not hasattr(g, 'after_request_callbacks'):
g.after_request_callbacks = []
g.after_request_callbacks.append(f)
return f
#app.after_request
def call_after_request_callbacks(r):
for callback in getattr(g, 'after_request_callbacks', ()):
callback(r)
return r
#app.before_request
def before_logout():
#after_this_request
def after_logout(response):
# check if called route == '/logout'
# in our case response.data == 'Logged out!'
# see: https://github.com/valhallasw/flask-mwoauth/blob/master/flask_mwoauth/__init__.py#L48
if request.url_rule.endpoint == 'mwoauth.logout':
# custom logic here...
# for example I change data in request
response.data = 'Data from after_logout'
Let's open /logout. You will see Data from after_logout.
Hope this helps.

Different login views for different flask blueprints

I have a flask web application and it has multiple blueprints:
restserver
webserver
Most of the endpoints in both webserver and restserver require the user to be logged-in. I use flask-login extension, which provides #login_required decorator.
Is it possible to have different login_view for different blueprints?
# for web blueprint
login_mananger.login_view = '/web/login'
.. so on
One of the most important specialities of RESTful is statelessness, it means the server won't "remember" any information from clients, the requests from clients should have contained all the needed informations, including auth informations.
Back to your question, you don't have to use Flask-Login for RESTful service authentication, and you should not use cookies or sessions because of the diversity of the clients. You can DIY the HTTP authentication of course, but Flask-HTTPAuth is what you really need.
Here is a simplest example of Flask-HTTPAuth:
from flask.ext.httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
#auth.verify_password
def verify_password(username, password):
user = User.query.filter_by(username=username).first()
if not user:
return False
g.current_user = user
# You should accomplish the verify_password function by yourself in the User model.
return user.verify_password(password)
According to the doc of Flask-HTTPAuth:
The callback function takes two arguments, the username and the
password and must return True or False.
You should notice that the initialization of the Flask-HTTPAuth just in the blueprint rather than the whole application since this authentication just used in your restserver blueprint.
Then there is a function just like Flask-login's #login_required which provided by Flask-HTTPAuth:
#app.route('/private')
#auth.login_required
def private_page():
return "Only for authorized people!"
This "login_required" callback function will be called when authentication is succesful. Since all of your restserver's route should be protected, you can use a "before_request" handler for applying the protection to the whole blueprint. Assuming that your restserver blueprint's name is "rest" and you have saved the user object in "g" just like what we do before:
from flask import jsonify
#rest.before_request
#auth.login_required
def before_request():
if not g.current_user:
return jsonify({'error':'Unauthorized.'})
I think this will do the trick. What I wrote above is just the simplest example and you can do much better in many ways, such as replacing the user credentials with token after first request. I believe that you will figure it out.
If my answer is helpful, it will be my great honour if you can "upvote" for this answer, thanks.
In your case, you need to place the login manager declaration in the same file as the flask app instance. This is commonly an __init__.py file with the app = Flask(__name__).
At the top, import LoginManager class
from flask_login import LoginManager
Then tie it to the app instance.
login_manager = LoginManager()
login_manager.init_app(app)
(This was not asked but just incase someone needs it) Lets say you have admins and normal users and you are authenticating from different tables:
#login_manager.user_loader
def load_user(user_id):
x = Users.query.get(str(user_id))
if x == None:
x = Admins.query.get(str(user_id))
return x
Finally after importing blueprints you can define the login views for each in a dictionary
login_manager.blueprint_login_views = {
'admin': '/admin/login',
'site': '/login',
}

Flask view function catch all URLs except one small subset

I'm writing a proxy in Flask. The proxy should forward all requests except one small subset, for which some additional checks should be performed.
Since there is a variety of routes I found this example in the docs
to match all URLs:
from flask import Flask
app = Flask(__name__)
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
def catch_all(path):
return 'You want path: %s' % path
if __name__ == '__main__':
app.run()
What I'm wondering is what is the best way to have one view function that handles this subset of routes, and another view function that handles all others ?
Flask has decorators to perform action before handling the request.
#app.after_request
def catch_all(response):
# * additional checks here *
You can stop the normal handling by returning data
If any of these function returns a value it’s handled as if it was the return value from the view and further request handling is stopped.
It is often use for authentication or permission checks.

Categories