I want to mock flask-login's current_user under the template rendering. This function return the current logged user.
Right now I'm mocking the AnnonymousUserMixin from flask-login which is returned by default if the user is not authenticated. But this leads to all kind of juggles. If I could simply mock current_user I would be able to create a Mocked object for it to return.
Here a sample of what I'm using today:
import unnittest
from flask_login.mixins import AnonymousUserMixin
class TestFoo(unittest.TestCase):
#patch.object(AnonymousUserMixin, 'is_admin', create=True,
return_value=False)
#patch.object(AnonymousUserMixin, 'is_authenticated', return_value=True)
def test_user_restriction(self, *args):
...
Regards,
Okay. I found the answer.
flask-login will ask you to initialize a LoginManager instance with login_manager.init_app(your_app). When you do this it add the current_user to your app contexts processors. This happens at flask_login.utils._user_context_processor function, which is defined as
def _user_context_processor():
return dict(current_user=_get_user())
Here _get_user is defined at the same module. What I do to mock current_user is mock _get_user at flask_login.utils.
Here is a working example of how it can be done. I am printing the response content so people can see the result differing. A real test would not instantiate Test class by hand and should use unittest.main or something appropriated.
from flask import Flask, render_template_string as render_string
from flask_login import LoginManager, UserMixin
app = Flask(__name__)
loginmgr = LoginManager(app)
loginmgr.init_app(app)
class User(UserMixin):
pass
#loginmgr.user_loader
def load_user(user_id):
return User.get(user_id)
#app.route('/')
def index():
return render_string('Hello, {{ current_user | safe }}')
if __name__ == '__main__':
import unittest
from unittest import mock
class Test:
def test(self):
client = app.test_client()
response = client.get('/')
data = response.data.decode('utf-8')
print(data)
#mock.patch('flask_login.utils._get_user')
def test_current_user(self, current_user):
user = mock.MagicMock()
user.__repr__ = lambda self: 'Mr Mocked'
current_user.return_value = user
client = app.test_client()
response = client.get('/')
data = response.data.decode('utf-8')
print(data)
t = Test()
t.test()
t.test_current_user()
Here is the output of it:
Hello, <flask_login.mixins.AnonymousUserMixin object at 0x7f9d5ddaaf60>
Hello, Mr Mocked
Regards,
I found this tutorial interesting in the section The Test.
It says this:
current_user needs to be accessed within the context of a request (it
is a thread-local object, just like flask.request). When
self.client.post completes the request and every thread-local object
is torn down. We need to preserve the request context so we can test
our integration with Flask-Login. Fortunately, Flask’s test_client is
a context manager, which means that we can use it in a with a statement
and it will keep the context around as long as we need it:
So in simple words, you can log in your user via post request and the current_user object will be available and then you can test everything you want in the code.
Here is an example:
with self.client:
response = self.client.post(url_for('users.login'),
data={'email': 'joe#joes.com', 'password': '12345'})
self.assert_redirects(response, url_for('index'))
self.assertTrue(current_user.name == 'Joe')
self.assertFalse(current_user.is_anonymous())
Related
Context:
I have flask application, which is an NGINX authentication module, written in flask, and in some part of the code I am making a call to an LDAP server.
The objective is to use flask-caching library to cache the response from the LDAP server and avoid that costly call.
The code bellow its a very stripped down version showing only the relevant sections.
Problem
In the below scenario I can't use the decorator #cache.memoized on a method of the Ldap class, as the variable cache is not available in that module.
name 'cache' is not defined
main.py
from flask import Flask
from flask_caching import Cache
from ldap import Ldap
app = Flask(__name__)
cache = Cache(app)
#app.route('/', defaults={'path': ''})
#app.route('/<path:path>')
#auth.login_required
def index(path):
code = 200
msg = "Another LDAP Auth"
headers = [('x-username', getRegister('username')),
('x-groups', getRegister('matchedGroups'))]
return msg, code, headers
#auth.verify_password
def login(username, password):
ldap = Ldap()
ldap.verifyUser(username, password)
ldap.py
class Ldap:
#cache.memoized(timeout=300) <<<< error
def validateUser(self, username, password)
if <ldap query goes here>
return True
return False
Research
The weird thing for me here is that this decorator depends on an object instance and not on a class as so many other scenarios I've seen
Alternative:
Definitively if I put the definition of the class in the same main.py and bellow the definition of the cache variable it works, however this will make my main.py file too long.
Attempt 1:
Trying to import my module after the definition of the cache variable has the same error
Attempt 2:
doing from main import cache inside ldap.py creates a circular import error.
Idea:
Pass the cache variable to the Ldap Class constructor and "somehow" use it to decorate the method, but I could not find how to do it exactly.
I'd like to setup variables available to both my views and my methods without polluting the request object in a before_request decorator.
Context processors seems like a nice way to do this however, I can't figure out how to actually access them from within my methods.
The best solution I have come up with is to memoize the context function so that it doesn't get called twice, once by me in my method and then again when Flask injects it into the template.
However, this will cache the method for all future requests and I only want it cached per request.
Here is my working example
from functools import cache
#app.context_processor
#cache
def setup_context():
return {
'planet': db.query('select planet from planets order by random()').first()
}
#app.route("/")
def index():
ctx = setup_context()
if ctx['planet'] == 'pluto':
return redirect('/pluto-is-not-a-planet')
return render_template('planet_greeting.html')
Any ideas on how to accomplish this without using functools.cache?
There may be a more elegant way to do this but here is what I have come up with so far.
The basic idea is to use the extensions pattern and create an object "Current" that the app gets passed to.
I can then use properties on this object to access the _app_ctx_stack as well as populate templates with context variables using the context_processor hook.
This approach will allow me to have templates that don't use "g" and a nice object to work with in my routes.
from flask import (
Flask, current_app, _app_ctx_stack,
render_template as template
)
from random import shuffle
planets = ['earth', 'pluto', 'mars']
class Current(object):
def __init__(self, app=None):
self.app = app
self.app.context_processor(self.context_processor)
def context_processor(self):
return _app_ctx_stack.top.__dict__
#property
def planet(self):
ctx = _app_ctx_stack.top
if not hasattr(ctx, 'planet'):
shuffle(planets)
ctx.planet = {
'name': planets[0]
}
return ctx.planet
app = Flask(__name__)
current = Current(app)
#app.route("/")
def index():
if current.planet['name'] == 'pluto':
return "Pluto is not a planet!"
return template("planet.html")
if __name__ == '__main__':
app.run(debug=True)
And in my template
{%# in my template %}
The planet is {{ planet.name }}!
Problem:
So my problem is I have a Flask microservice want to implement the unit tests to it so when I start writing my test cases I found that I need to authenticate the unit test client because of some endpoints need authorization and here comes the problem the whole authentication system in another service this service all can do about the authentication is to validate the JWT token and get user ID from it so here is one of the views.py
from flask_restful import Resource
from common.decorators import authorize
class PointsView(Resource):
decorators = [authorize]
def get(self, user):
result = {"points": user.active_points}
return result
and authorize decorator from decorators.py
import flask
import jwt
from jwt.exceptions import DecodeError, InvalidSignatureError
from functools import wraps
from flask import request
from flask import current_app as app
from app import db
from common.models import User
from common.utils import generate_error_response
def authorize(f):
"""This decorator for validate the logged in user """
#wraps(f)
def decorated_function(*args, **kwargs):
if 'Authorization' not in request.headers:
return "Unable to log in with provided credentials.", 403
raw_token = request.headers.get('Authorization')
if raw_token[0:3] != 'JWT':
return generate_error_response("Unable to log in with provided credentials.", 403)
token = str.replace(str(raw_token), 'JWT ', '')
try:
data = jwt_decode_handler(token)
except (DecodeError, InvalidSignatureError):
return generate_error_response("Unable to log in with provided credentials.", 403)
user = User.query.filter_by(id=int(data['user_id'])).first()
return f(user, *args, **kwargs)
return decorated_function
and the test case from tests.py
import unittest
from app import create_app, db
from common.models import User
class TestMixin(object):
"""
Methods to help all or most Test Cases
"""
def __init__(self):
self.user = None
""" User Fixture for testing """
def user_test_setup(self):
self.user = User(
username="user1",
active_points=0
)
db.session.add(self.user)
db.session.commit()
def user_test_teardown(self):
db.session.query(User).delete()
db.session.commit()
class PointsTestCase(unittest.TestCase, TestMixin):
"""This class represents the points test case"""
def setUp(self):
"""Define test variables and initialize app."""
self.app = create_app("testing")
self.client = self.app.test_client
with self.app.app_context():
self.user_test_setup()
def test_get_points(self):
"""Test API can create a points (GET request)"""
res = self.client().get('/user/points/')
self.assertEqual(res.status_code, 200)
self.assertEquals(res.data, {"active_points": 0})
def tearDown(self):
with self.app.app_context():
self.user_test_teardown()
# Make the tests conveniently executable
if __name__ == "__main__":
unittest.main()
My authentication system work as the following:
Any service (include this one) request User service to get user JWT
token
Any service take the JWT token decoded and get the user ID
from it
Get the user object from the database by his ID
so I didn't know how to make the authentication flow in the test cases.
Here is just an example. I skipped some little things such as create_app, jwt.decode(token) etc. I'm sure you can understand the main approach. Structure:
src
├── __init__.py # empty
├── app.py
└── auth_example.py
app.py:
from flask import Flask
from src.auth_example import current_identity, authorize
app = Flask(__name__)
#app.route('/')
#authorize()
def main():
"""
You can use flask_restful - doesn't matter
Do here all what you need:
user = User.query.filter_by(id=int(current_identity['user_id'])).first()
etc..
just demo - return current user_id
"""
return current_identity['user_id']
auth_example.py:
from flask import request, _request_ctx_stack
from functools import wraps
from werkzeug.local import LocalProxy
current_identity = LocalProxy(lambda: getattr(_request_ctx_stack.top, 'current_identity', None))
def jwt_decode_handler(token):
"""
just do here all what you need. Should return current user data
:param str token:
:return: dict
"""
# return jwt.decode(token), but now - just demo
raise Exception('just demo')
def authorize():
def _authorize(f):
#wraps(f)
def __authorize(*args, **kwargs):
if 'Authorization' not in request.headers:
return "Unable to log in with provided credentials.", 403
raw_token = request.headers.get('Authorization')
if raw_token[0:3] != 'JWT':
return "Unable to log in with provided credentials.", 403
token = str.replace(str(raw_token), 'JWT ', '')
try:
# I don't know do you use Flask-JWT or not
# this is doesn't matter - all what you need is just to mock jwt_decode_handler result
_request_ctx_stack.top.current_identity = jwt_decode_handler(token)
except Exception:
return "Unable to log in with provided credentials.", 403
return f(*args, **kwargs)
return __authorize
return _authorize
Our test:
import unittest
from mock import patch
from src.app import app
app.app_context().push()
class TestExample(unittest.TestCase):
def test_main_403(self):
# just a demo that #authorize works fine
result = app.test_client().get('/')
self.assertEqual(result.status_code, 403)
def test_main_ok(self):
expected = '1'
# we say that jwt_decode_handler will return {'user_id': '1'}
patcher = patch('src.auth_example.jwt_decode_handler', return_value={'user_id': expected})
patcher.start()
result = app.test_client().get(
'/',
# send a header to skip errors in the __authorize
headers={
'Authorization': 'JWT=blabla',
},
)
# as you can see current_identity['user_id'] is '1' (so, it was mocked in view)
self.assertEqual(result.data, expected)
patcher.stop()
So, in your case you need just mock jwt_decode_handler. Also I recommend do not add any additional arguments inside a decorators. It will be hard to debugging when you have more than two decorators with a different arguments, recursion, hard processing etc.
Hope this helps.
Could you create some mock tokens in your unit testing framework (that your decorator can actually decode like in a real request) and send them in with your test client? An example of how that might look can be seen here: https://github.com/vimalloc/flask-jwt-extended/blob/master/tests/test_view_decorators.py#L321
I know logins are disabled by default in testing. I'm trying to get them back on, by setting app.config['LOGIN_DISABLED'] to False. This doesn't seem to be working, since current_user still returns None, but will return a User in code outside a test file.
Some relevant bits of code are below. My Flask app object is originally created in my app's main __init__.py, which is imported and re-configured during testing.
===========
init.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
app = Flask(__name__)
app.config[u'DEBUG'] = settings.debug
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
===========
tests/base.py
from my_app import app, db
import unittest
class BaseTest(unittest.TestCase):
def setUp(self):
app.config['TESTING'] = True
app.config['LOGIN_DISABLED'] = False
#app.login_manager._login_disabled = False #doesn't help either
self.app = app.test_client()
db.create_all()
===========
tests/test_a.py
from flask_login import current_user
from my_app.tests.base import BaseTest
class MyTests(BaseTest):
def test_a(self):
#visit endpoint that calls `login_user(user)`
#printing `current_user` in that endpoint works, but the following line only returns `None`
print current_user
Note: The User should definitely be logged in before the print statement. Using current_user in the endpoint works as expected.
Turns out my problem was due to testing not using the same context, so my solution is similar to what is described here and here. tests/test_a.py now looks like:
from flask_login import current_user
from my_app.tests.base import BaseTest
class MyTests(BaseTest):
def test_a(self):
with self.app:
print current_user
Strangely, app.config['LOGIN_DISABLED'] = False and app.login_manager._login_disabled = False don't matter after this change - both can be removed.
I would like to store some information at the "request scope" when using google app engine (python). What I mean by this is that I would like to initialize some information when a request is first received, and then be able to access it from anywhere for the life of the request (and only from that request).
An example of this would be if I saved the current user's name at request scope after they were authenticated.
How would I go about doing this sort of thing?
Thanks!
A pattern used in app engine itself seems to be threading.local which you can grep for in the SDK code. Making os.environ request local is done like that in runtime/request_environment.py for example.
A rough example:
import threading
class _State(threading.local):
"""State keeps track of request info"""
user = None
_state = _State()
From elsewhere you could authenticate early on in handler code.
from state import _state
if authentication_passed:
_state.user = user
and provide convenience that can be used in other parts of your code
from state import _state
def get_authenticated_user():
user = _state.user
if not user:
raise AuthenticationError()
return user
You need something like this:-
class BaseHandler(webapp2.RequestHandler):
#A function which is useful in order to determine whether user is logged in
def initialize(self, *a, **kw):
#Do the authentication
self.username = username
class MainHandler(BaseHandler):
def get(self):
print self.username
Now if you inherit BaseHandler class all the request will first go through the initialize method of BaseHandler class and since in the BaseHandler class you are setting the username
and MainHandler inherits form BaseHandler you will have the self.username defined and all the request wil go through initialize method.