Strange behaviour of cherrypy.session while testing between requests - python

I'm facing a strange issue while testing a CherryPy app when testing.
Basically, session data is lost between requests while testing, as when running the server and testing manually this does not happen.
The app itself is quite simple, but some resource are protected using the mechanism of hook to be triggered before the request is processed.
Let's see the main file:
import cherrypy
import hashlib
import json
import sys
from bson import json_util
from cr.db.store import global_settings as settings
from cr.db.store import connect
SESSION_KEY = 'user'
main = None
def protect(*args, **kwargs):
"""
Just a hook for checking protected resources
:param args:
:param kwargs:
:return: 401 if unauthenticated access found (based on session id)
"""
# Check if provided endpoint requires authentication
condition = cherrypy.request.config.get('auth.require', None)
if condition is not None:
try:
# Try to get the current session
cherrypy.session[SESSION_KEY]
# cherrypy.session.regenerate()
cherrypy.request.login = cherrypy.session[SESSION_KEY]
except KeyError:
raise cherrypy.HTTPError(401, u'Not authorized to access this resource. Please login.')
# Specify the hook
cherrypy.tools.crunch = cherrypy.Tool('before_handler', protect)
class Root(object):
def __init__(self, db_settings):
self.db = connect(db_settings)
#cherrypy.expose
#cherrypy.config(**{'auth.require': True, 'tools.crunch.on': False})
def index(self):
# If authenticated, return to users view
if SESSION_KEY in cherrypy.session:
raise cherrypy.HTTPRedirect(u'/users', status=301)
else:
return 'Welcome to this site. Please login.'
#cherrypy.tools.allow(methods=['GET', 'POST'])
#cherrypy.expose
#cherrypy.config(**{'auth.require': True})
#cherrypy.tools.json_in()
def users(self, *args, **kwargs):
if cherrypy.request.method == 'GET':
return json.dumps({'users': [u for u in self.db.users.find()]}, default=json_util.default)
elif cherrypy.request.method == 'POST':
# Get post form data and create a new user
input_json = cherrypy.request.json
new_id = self.db.users.insert_one(input_json)
new_user = self.db.users.find_one(new_id.inserted_id)
cherrypy.response.status = 201
return json.dumps(new_user, default=json_util.default)
#cherrypy.tools.allow(methods=['GET', 'POST'])
#cherrypy.expose
#cherrypy.config(**{'tools.crunch.on': False})
def login(self, *args, **kwargs):
if cherrypy.request.method == 'GET':
# Check if user is logged in already
if SESSION_KEY in cherrypy.session:
return """<html>
<head></head>
<body>
<form method="post" action="logout">
<label>Click button to logout</label>
<button type="submit">Logout</button>
</form>
</body>
</html>"""
return """<html>
<head></head>
<body>
<form method="post" action="login">
<input type="text" value="Enter email" name="username" />
<input type="password" value="Enter password" name="password" />
<button type="submit">Login</button>
</form>
</body>
</html>"""
elif cherrypy.request.method == 'POST':
# Get post form data and create a new user
if 'password' and 'username' in kwargs:
user = kwargs['username']
password = kwargs['password']
if self.user_verify(user, password):
cherrypy.session.regenerate()
cherrypy.session[SESSION_KEY] = cherrypy.request.login = user
# Redirect to users
raise cherrypy.HTTPRedirect(u'/users', status=301)
else:
raise cherrypy.HTTPError(u'401 Unauthorized')
else:
raise cherrypy.HTTPError(u'401 Please provide username and password')
#cherrypy.tools.allow(methods=['GET'])
#cherrypy.expose
def logout(self):
if SESSION_KEY in cherrypy.session:
cherrypy.session.regenerate()
return 'Logged out, we will miss you dearly!.'
else:
raise cherrypy.HTTPRedirect(u'/', status=301)
def user_verify(self, username, password):
"""
Simply checks if a user with provided email and pass exists in db
:param username: User email
:param password: User pass
:return: True if user found
"""
users = self.db.users
user = users.find_one({"email": username})
if user:
password = hashlib.sha1(password.encode()).hexdigest()
return password == user['hash']
return False
if __name__ == '__main__':
config_root = {'/': {
'tools.crunch.on': True,
'tools.sessions.on': True,
'tools.sessions.name': 'crunch', }
}
# This simply specifies the URL for the Mongo db
settings.update(json.load(open(sys.argv[1])))
main = Root(settings)
cherrypy.quickstart(main, '/', config=config_root)
cr.db is a very simple wrapper over pymongo that exposes the db functionality, nothing special.
As you can see the users view is protected, basically if the SESSION['user'] key is not set, we ask to login.
If I fire up the server and try to access /users directly I'm redirected to /login. Once loged in, visiting /users again works fine, since
cherrypy.session[SESSION_KEY]
Does not throw an KeyError since it was properly set in /login. Everything cool.
Now this is my test file, based on official docs about testing, located at same level as file above.
import urllib
from unittest.mock import patch
import cherrypy
from cherrypy.test import helper
from cherrypy.lib.sessions import RamSession
from .server import Root
from cr.db.store import global_settings as settings
from cr.db.loader import load_data
DB_URL = 'mongodb://localhost:27017/test_db'
SERVER = 'http://127.0.0.1'
class SimpleCPTest(helper.CPWebCase):
def setup_server():
cherrypy.config.update({'environment': "test_suite",
'tools.sessions.on': True,
'tools.sessions.name': 'crunch',
'tools.crunch.on': True,
})
db = {
"url": DB_URL
}
settings.update(db)
main = Root(settings)
# Simply loads some dummy users into test db
load_data(settings, True)
cherrypy.tree.mount(main, '/')
setup_server = staticmethod(setup_server)
# HELPER METHODS
def get_redirect_path(self, data):
"""
Tries to extract the path from the cookie data obtained in a response
:param data: The cookie data from the response
:return: The path if possible, None otherwise
"""
path = None
location = None
# Get the Location from response, if possible
for tuples in data:
if tuples[0] == 'Location':
location = tuples[1]
break
if location:
if SERVER in location:
index = location.find(SERVER)
# Path plus equal
index = index + len(SERVER) + 6
# Get the actual path
path = location[index:]
return path
def test_login_shown_if_not_logged_in(self):
response = self.getPage('/')
self.assertStatus('200 OK')
self.assertIn('Welcome to Crunch. Please login.', response[2].decode())
def test_login_redirect_to_users(self):
# Try to authenticate with a wrong password
data = {
'username': 'john#doe.com',
'password': 'admin',
}
query_string = urllib.parse.urlencode(data)
self.getPage("/login", method='POST', body=query_string)
# Login should show 401
self.assertStatus('401 Unauthorized')
# Try to authenticate with a correct password
data = {
'username': 'john#doe.com',
'password': '123456',
}
query_string = urllib.parse.urlencode(data)
# Login should work and be redirected to users
self.getPage('/login', method='POST', body=query_string)
self.assertStatus('301 Moved Permanently')
def test_login_no_credentials_throws_401(self):
# Login should show 401
response = self.getPage('/login', method='POST')
self.assertStatus('401 Please provide username and password')
def test_login_shows_login_logout_forms(self):
# Unauthenticated GET should show login form
response = self.getPage('/login', method='GET')
self.assertStatus('200 OK')
self.assertIn('<form method="post" action="login">', response[2].decode())
# Try to authenticate
data = {
'username': 'john#doe.com',
'password': '123456',
}
query_string = urllib.parse.urlencode(data)
# Login should work and be redirected to users
response = self.getPage('/login', method='POST', body=query_string)
self.assertStatus('301 Moved Permanently')
# FIXME: Had to mock the session, not sure why between requests while testing the session loses
# values, this would require more investigation, since when firing up the real server works fine
# For now let's just mock it
sess_mock = RamSession()
sess_mock['user'] = 'john#doe.com'
with patch('cherrypy.session', sess_mock, create=True):
# Make a GET again
response = self.getPage('/login', method='GET')
self.assertStatus('200 OK')
self.assertIn('<form method="post" action="logout">', response[2].decode())
As you can see in last method, after login, we should have cherrpy.session[SESSION_KEY] set, but for some reason the session does not have the key. That's the reason I actually had to mock it...this works, but is hacking something that should actually work...
To me it looks like when testing the session is not being kept between requests. Before digging into CherrPy internals I wanted to ask this in case someone stumbled upon something similar in the past.
Notice I'm using Python 3.4 here.
Thanks

getPage() accepts headers argument and produces self.cookies iterable. But it does not pass it over to the next request automatically, so it doesn't get the same session cookies.
I've crafted a simple example of how to persist session with the next request:
>>> test_cp.py <<<
import cherrypy
from cherrypy.test import helper
class SimpleCPTest(helper.CPWebCase):
#staticmethod
def setup_server():
class Root:
#cherrypy.expose
def login(self):
if cherrypy.request.method == 'POST':
cherrypy.session['test_key'] = 'test_value'
return 'Hello'
elif cherrypy.request.method in ['GET', 'HEAD']:
try:
return cherrypy.session['test_key']
except KeyError:
return 'Oops'
cherrypy.config.update({'environment': "test_suite",
'tools.sessions.on': True,
'tools.sessions.name': 'crunch',
})
main = Root()
# Simply loads some dummy users into test db
cherrypy.tree.mount(main, '')
def test_session_sharing(self):
# Unauthenticated GET
response = self.getPage('/login', method='GET')
self.assertIn('Oops', response[2].decode())
# Authenticate
response = self.getPage('/login', method='POST')
self.assertIn('Hello', response[2].decode())
# Make a GET again <<== INCLUDE headers=self.cookies below:
response = self.getPage('/login', headers=self.cookies, method='GET')
self.assertIn('test_value', response[2].decode())
Running it
$ pytest
Test session starts (platform: linux, Python 3.6.1, pytest 3.0.7, pytest-sugar 0.8.0)
rootdir: ~/src/test, inifile:
plugins: sugar-0.8.0, backports.unittest-mock-1.3
test_cp.py ✓✓ 100% ██████████
Results (0.41s):
2 passed
P.S. Of course, ideally I'd inherit testcase class and add additional method to encapsulate this ;)

Related

"description": "Invalid credentials" in POSTMAN using flask_jwt

I am trying to authenticate and get an access token. I have created user class, I am trying to run the POST method from POSTMAN while authenticating, but I am receiving some error:
{
"description": "Invalid credentials",
"error": "Bad Request",
"status_code": 401
}
and I couldn't find any solution.
Code for app.py
from flask import Flask, request
from flask_restful import Resource, Api
from flask_jwt import JWT, jwt_required
from security import authenticate, identity
# creating flask app
app = Flask(__name__)
app.secret_key = 'vishwas'
api = Api(app)
jwt = JWT(app, authenticate, identity) # /auth
# empty list of items
items = []
class Item(Resource):
#jwt_required()
def get(self,name):
# next return first value that found by the filter function
# next(filter(), None) -> 'None' to handle the eroor if the list is empty
item = next(filter(lambda x: x['name'] == name,items), None)
return {'item': item}, 200 if item else 404
# http://127.0.0.1.5000/item/<string:name>
api.add_resource(Item, '/item/<string:name>')
app.run(port=5000, debug=True)
Code for security.py
from werkzeug.security import safe_str_cmp
from user import User
# list of users
users = [
User(1,"bob","pass")
]
# users information using their username
username_mapping = {user.username: user for user in users}
# users information using their userid
userid_mapping = {user.id: user for user in users}
def authenticate(username,password):
user = userid_mapping.get(username, None)
if user and safe_str_cmp(user.password, password):
return user
def identity(payload):
user_id = payload['identity']
return userid_mapping.get(user_id, None)
Code for user.py
class User:
def __init__(self,_id,username,password):
self.id = _id
self.username = username
self.password = password
As you can see that I have implemented the code correctly but still I am getting this 'Invalid Credentials' or 'Bad Request' error.
if you look at what is stored in the data:
def authenticate(username, password):
print(username_mapping)
we will see:
{('bob',): <user.User object at 0x000002C7DC982B00>}
that is, the ('bob',) key, not bob
I myself only study potshon, so the decision to make only this
def authenticate(username, password):
user = username_mapping.get(('{}'.format(username),), None)
if user and safe_str_cmp(user.password, password):
return user
And you mistake there, instead username_mapping, you're using userid_mapping
And accordingly:
def identity(payload):
user_id = payload['identity']
return userid_mapping.get((user_id[0],), None)
I don’t know how much is correct, most likely it is necessary to bring the initial data to the correct type, but it works. Maybe someone will tell you how it is more correct.
Make sure to use username instead of name
"name": "mostafa",
"password": "pass"
}
should be this
"username": "mostafa",
"password": "pass"
}

Tornado login form unit test

I have the following Tornado 6.0.3 Web server:
import tornado.ioloop
import tornado.web
import json
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
return self.get_secure_cookie("user")
def set_current_user(self, user):
print('setting secure cookie', user)
self.set_secure_cookie("user", user)
class LoginHandler(BaseHandler):
def get(self):
self.render('login.html')
def post(self):
user = self.get_argument("user")
self.set_current_user(user)
self.redirect("/")
class IndexHandler(BaseHandler):
def get(self):
user = self.get_current_user()
print('getting current user in IndexHandler', self.get_current_user())
if user:
self.write('Hi, ' + user.decode('utf-8'))
else:
self.write("Hi, unknown. <a href='/login'>Login</a>")
class LogoutHandler(BaseHandler):
#tornado.web.authenticated
def get(self):
self.clear_cookie("user")
def make_app():
settings = {
"cookie_secret":"61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
"login_url":"/login",
"debug":False,
'xsrf_cookies': True,
}
return tornado.web.Application([
(r"/", IndexHandler),
(r"/login", LoginHandler),
(r"/logout", LogoutHandler),
], **settings)
if __name__ == "__main__":
application = make_app()
application.listen(8005)
print('sample_app server started')
tornado.ioloop.IOLoop.instance().start()
login.html is defined this way:
<form method='POST'>
user <input type='text' name='user' id='user'><br>
password <input type='text' name='password' id='password'><br>
<input type='submit' value='Valider'>
{% raw xsrf_form_html() %}
</form>
This application works correctly. When I go to the login page and type 'bla' for user and 'bla bla' for password, I get: 'Hi, bla' in the browser, and the trace is:
sample_app server started
getting current user in IndexHandler None
setting secure cookie bla
getting current user in IndexHandler b'bla'
Now, when I write the following test:
import unittest
from unittest import TestCase
from tornado.testing import AsyncHTTPTestCase
from sample_app import *
from urllib.parse import urlencode
class AppTestCase(AsyncHTTPTestCase):
def get_app(self):
self.app = make_app()
return self.app
def test_login(self):
p = {'user': 'bla', 'password': 'bla bla', '_xsrf': "dummy"}
response = self.fetch('/login', method='POST', body=urlencode(p), headers={"Cookie": "_xsrf=dummy"})
print("\n", response.body)
if __name__ == "__main__":
unittest.main()
I get for response: Hi, unknown. Login,
and the trace is:
setting secure cookie bla
getting current user in IndexHandler None
Why secure cookie is not correctly set in test mode ?
Tornado's default HTTP client doesn't support cookies (secure or not). You can set follow_redirects=False and see that the Set-Cookie header is set on the response from /login, but there's not currently a way to pass that cookie to the index handler automatically with the default client.
If you use the curl HTTP client instead, I think you can configure this to process cookies, but I haven't tried this myself and don't know how to configure it to do this.

Django using one time call function output as global value

While initializing the project, i want to call login function from projects settings for once and use the output token anywhere in project. I want to use same token whether I call again to check if token changed.
def login(creds):
r = requests.post(base_url + '/api/admin/login', json=creds, headers=headers, verify=False)
if r.status_code == 200:
token = r.json()['token']
return token
If i call function in project settings, function starts each time. I don't want to dump token to a file and read each time. Any other way? Thanks.
The best way would be to separate responsibilities for login, authentication and use of token. In practice you'd store token in session. Django provides full support for anonymous sessions, but this could limit your application in cases, when you'd like to store some data associated to that user in the local database.
What I recommend is to setup User model in the local database where you save usernames. Next you create a function for authentication:
import requests
def authentication(username, password):
creds = {'username': username, 'password': password}
headers = 'whatever'
r = requests.post(base_url + '/api/admin/login', json=creds, headers=headers, verify=False)
if r.status_code == 200:
token = r.json()['token']
# Create user in the local database if it does not exist yet
user, created = User.objects.get_or_create(username=username)
return token
else:
error = 'Something went wrong'
return error
Next you create login functionality.
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.contrib.auth import login
from django.shortcuts import render
def login(request):
# Check if user is authenticated by call is_authenticated() and
# additionally check if token is stored in session
if request.user.is_authenticated and request.session.get('token', False):
return HttpResponseRedirect(reverse('user_page'))
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
auth_data = authenticate(username=username, password=password)
if auth_data is not None:
# Save token in session
request.session['user_token'] = auth_data
# Get user and login
# Because you created a user in local database,
# you can login that user in this step
user = User.objects.get(username=username)
login(request, user)
next = request.POST.get('next', '/') if request.POST.get('next') else '/'
return HttpResponseRedirect(next)
else:
# Implement some error handling here
return HttpResponseRedirect(reverse('login'))
else:
return render(request, 'login.html', {})
The next logical step would be to control this users tokens somehow. Decorators come to rescue. You create a decorator that checks token. For example something in this sense:
import functools
from django.contrib.auth import REDIRECT_FIELD_NAME
from urllib.parse import urlparse
from django.shortcuts import resolve_url
from django.contrib.auth.views import redirect_to_login
def token_required(func, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
#functools.wraps(func)
def decorated_function(request, *args, **kwargs):
# Check if token is saved in session
# Redirect to login page if not
if not request.session.get('token', False):
path = request.build_absolute_uri()
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
return redirect_to_login(
path, resolved_login_url, redirect_field_name)
return func(request, *args, **kwargs)
return decorated_function
You'd need to implement your logic here. This decorator only check if token is stored in session, nothing else. You'd then use this decorator in views as in
from django.contrib.auth import logout
#token_required
#login_required
def user_logout(request):
logout(request)
return HttpResponseRedirect('/')

Django: ValidationError using is_authenticated and logout when authenticating from an external source

I am currently building a Django application that gets all its data from an external source (via HTTP). I therefore do not want to use my own database. I have already implemented my own user and remote user backend as per the solution stated in this SO question: Django users and authentication from external source. I am using signed cookies for storing sessions.
After implementing the above stated, I was able to login without any errors. However, on checking whether a user is authenticated in a template (or when trying to log him out in a view), I receive an error:
ValidationError at /products/
["'None' value must be an integer."]
at the line in the template
{% if user.is_authenticated %}
where request.user is passed as a template variable user in the corresponding view (I know I shouldn't do that, it's just temporary).
Here is some of the other relevant traceback:
/home/ines/.virtualenvs/diet/lib/python3.6/site-packages/django/contrib/auth/middleware.py in <lambda>
request.user = SimpleLazyObject(lambda: get_user(request))
...
/home/ines/.virtualenvs/diet/lib/python3.6/site-packages/django/contrib/auth/middleware.py in get_user
request._cached_user = auth.get_user(request)
...
/home/ines/.virtualenvs/diet/lib/python3.6/site-packages/django/contrib/auth/__init__.py in get_user
user_id = _get_user_session_key(request)
...
/home/ines/.virtualenvs/diet/lib/python3.6/site-packages/django/contrib/auth/__init__.py in _get_user_session_key
return get_user_model()._meta.pk.to_python(request.session[SESSION_KEY])
The error happens in the to_python() call, where the line 942 in file django/db/models/fields/__init__.py: return int(value) throws an Exception as it receives value='None'.
Let me now post all (hopefully) relevant code from my app.
settings.py
AUTHENTICATION_BACKENDS = ('products.backends.ApiAuthBackend',)
products/backends.py
from django.contrib.auth.backends import RemoteUserBackend
from products.api import API
from products.models import ApiUser
class ApiAuthBackend(RemoteUserBackend):
"""
Authenticate against the API.
"""
create_unknown_user = False # does not create a User object if it is not in the database
def authenticate(self, request, username=None, password=None):
api = API()
access_token = api.login(username=username, password=password)
if api.logged_in():
user = ApiUser()
user.username = username
request.session['access_token'] = access_token
return user
return None
def get_user(self, user_id):
return ApiUser(username=user_id)
products/models.py
from django.contrib.auth.models import AbstractUser
class ApiUser(AbstractUser):
def save(self, **kwargs):
"""
Disables saving to database.
"""
pass
objects = None
username = ''
first_name = ''
last_name = ''
email = ''
password = ''
groups = ''
user_permissions = ''
is_staff = False
is_active = True
is_superuser = False
last_login = None
date_joined = None
def get_group_permissions(self, obj=None):
return []
def get_all_permissions(self, obj=None):
return []
def has_perm(self, perm, obj=None):
return []
def has_perms(self, obj=None, **kwargs):
return []
def has_module_perms(self, app_label):
return []
Upon further inspection of the django/contrib/auth/__init__.py, line 154:
request.session[SESSION_KEY] = user._meta.pk.value_to_string(user)
The problem seems to be that the request.session[SESSION_KEY], where SESSION_KEY = '_auth_user_id', is set to 'None'. That's due to the fact that the ApiUser class does not have Fields for attributes anymore - I have overriden them in models.py since I am not using a database. Therefore my instances of ApiUser do not have a pk, and SESSION_KEY attribute is therefore set to 'None'. When Django wants to authenticate, the SESSION_KEY attribute of the session gets passed to the aforementioned to_python() function which in turn raises an Exception.
My question is, how do I fix this? I tried setting the user.pk to always be 1 and setting the session[SESSION_KEY] manually to 1 in the authenticate function in models.py, which has resulted in me not getting an exception but other data written to the session (access_token) has disappeared.
Thank you in advance for your time and answers, and I'm terribly sorry if there happens to be a solution to this somewhere already (I did search extensively but haven't been able to find it).
Update (as per request by Bear Brown):
Here is my code for API class, not sure it's really relevant as it's just used to communicate with the external database but anyway.
products/api.py
import requests
import json
class API:
URL = "[...]"
KEY = "[...]"
LOGIN = "authenticate/access-token"
REGISTER = "authenticate/register"
session = None
debug = True
def __init__(self, **kwargs):
if len(kwargs) > 2 or not kwargs:
return
try:
self.login(**kwargs)
except requests.exceptions.ConnectionError:
print("Could not connect to the database.")
except requests.exceptions.RequestException:
print("Could not retrieve data from the database.")
def login(self, **kwargs):
if self.session is not None:
return
assert 0 < len(kwargs) < 3
access_token = ''
if len(kwargs) == 2:
username, password = kwargs['username'], kwargs['password']
url = self.URL + self.LOGIN
user_data = {
'username': username,
'password': password,
'active-check': 0,
'api_key': self.KEY
}
r = requests.post(url, data=user_data)
r.raise_for_status()
response = r.json()
if response['success'] is True:
access_token = response['access_token']
else: # len(kwargs) == 1
access_token = kwargs['access_token']
if access_token:
self.session = requests.Session()
self.session.headers.update({'Authorization': 'Client-ID ' + access_token})
if self.debug: print("Successfully logged in.")
return access_token
def logout(self):
if self.session is None:
return
self.session.close()
self.session = None
def logged_in(self):
return self.session is not None
Here is a link to the full error traceback on pastebin (it seemed too be too long to add to an already long post):
full error traceback on pastebin.

flask-login user is set to anonymous after login

im new to flask and flask-login and ive been struggling with this for days.
Im trying to log a user in like this:
from creds import auth_username, auth_password, pgsql_dbuser, pgsql_dbpassword, pgsql_db1name
from flask import Flask, render_template, request, Response, redirect, url_for
from flask.ext.bcrypt import Bcrypt
from flask.ext.login import LoginManager, login_required, login_user, current_user, logout_user
import logging
import psycopg2
import uuid
import datetime
app = Flask(__name__)
app.secret_key = str(uuid.uuid4()) # <- required by login_manager.init_app(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'index'
#app.route('/', methods=['GET','POST'])
def index():
page_name = '/'
if request.method == 'POST':
email = request.form['email']
candidate_password = request.form['password']
user = finduserindbbyemail(email)
if user != None:
password_hash = checkuserpasswordindb(email)
if bcrypt.check_password_hash(password_hash, candidate_password):
user_object = User(user)
result = login_user(user_object) # <- here for successful login
return redirect(url_for('loggedin', user_object=type(user_object), user=user, result=result, current_user=current_user))
else:
user_object = User(user)
error_message = "The password you entered is incorrect"
return render_template('index.html', error_message=error_message)
else:
error_message = "The email address you entered does not match any we have in our records"
return render_template('index.html', error_message=error_message)
if request.method == 'GET':
return render_template('index.html')
I have a User class and a user callback:
class User():
def __init__(self, user):
self.user = user
def is_authenticated(self):
return True
def is_active(self):
return True
def is_anonymous(self):
return False
def get_id(self):
return unicode(self.user)
#login_manager.user_loader
def load_user(user):
con = psycopg2.connect(database=pgsql_db1name, user=pgsql_dbuser, password=pgsql_dbpassword, host='localhost')
uuid = "'"+user+"'"
cur = con.cursor()
cur.execute("SELECT uuid FROM users WHERE uuid = "+ uuid)
uuid = cur.fetchone()
con.close()
if uuid != None:
user = unicode(uuid[0])
return User.get_id(user)
else:
return None
After authentication is successful (apparently?), the user is redirected to a loggedin page which has a #login_required decorator. But instead of loading the loggedin page, the app redirects the user to the login page, telling me the user isnt being logged in?
If try to send values to the page and i remove the #login_required decorator so i can see the page, this is what i see in the browser after 'logging in':
current_user.is_authenticated() = False
current_user.is_active() = False
current_user.is_anonymous() = True
current_user.get_id() = None
user_object = <type 'instance'>
user = 2ca1296c-374d-43b4-bb7b-94b8c8fe7e44
login_user = True
current_user = <flask_login.AnonymousUserMixin object at 0x7f2aec80f190> Logout
It looks like my user hasn't been logged and is being treated as anonymous?
Can anyone see what I've done wrong? I'm having a lot of trouble understanding how this is supposed to work.
Another reason you might not be able to log a user in or current_user is Anonymous after going through your login form: The active=false flag is set on the user in the db. This behavior is confirmed in the docs:
flask_login.login_user(user, remember=False, duration=None, force=False, fresh=True)[source]
Logs a user in. You should pass the actual user object to this. If the user’s is_active property is False, they will not be logged in unless force is True.
This will return True if the log in attempt succeeds, and False if it fails (i.e. because the user is inactive).
So, when you call login_user, you can do this:
login_user(user, remember=form.remember_me.data, force=True), if you want to allow inactive users to log in.
So.. I managed to get it to work, but not using the user_loader callback. For whatever reason, my user loader exhibits the same behaviour as this:
Flask-login with static user always yielding 401- Unauthorized
Anyway, I used a request_loader callback instead based on this example:
http://gouthamanbalaraman.com/blog/minimal-flask-login-example.html
so for a user logging in, which starts here:
if bcrypt.check_password_hash(password_hash, candidate_password):
user_object = User(user, password_hash)
result = login_user(user_object) # <- here for successful login
token = user_object.get_auth_token(user, password_hash)
return redirect(url_for('loggedin', token=token))
I create a user object which has the user's id and their password hash.
then i log the user in. then i create a time-serialized token of the user id and password hash using itsdangerous. the get_auth_token function is part of the User class. it looks like this:
class User():
def __init__(self, user, password_hash):
self.user = user
self.password = password_hash
.
.
.
def get_auth_token(self, user, password):
data = [str(self.user), self.password]
return serializer.dumps(data, salt=serializer_secret)
you need to create a serializer at the beginning of your code somewhere:
serializer = URLSafeTimedSerializer(serializer_secret)
So after the token is created, pass it to the loggedin view as a URL query parameter.
When you try to load a login_required page, like my loggedin page, which is where login_user redirects me to after a successful login, the request_loader callback is executed. it looks like this:
#login_manager.request_loader
def load_user_from_request(request):
if request.args.get('token'):
token = request.args.get('token')
max_age = 1
try:
data = serializer.loads(token, salt=serializer_secret, max_age=max_age)
username = data[0]
password_hash = data[1]
found_user = finduserindbbyuuid(username)
found_password = checkuserpasswordindbbyuuid(username)
if found_user and found_password == password_hash:
user_object = User(found_user, password_hash)
if (user_object.password == password_hash):
return user_object
else:
return None
else:
return None
except BadSignature, e:
pass
else:
return None
This is the point where my user_loader was failing. I was logging in successfully, but the user_loader was always returning None and so my user would be deemed as anonymous.
So with the request loader, it checks that the request URL contains a 'token' argument in the query string. if so, it takes its value and using itsdangerous, deserializes the data.
you can make the token expire with timed serializers, but there are also non timed ones. after the token is deserialized, take the user and password hash and check in the database if they exist, in exactly the same way that the user_loader was supposed to work.. i imagine? my user_loader didnt work so i was most probably doing it wrong.
anyway if a user and password match in the db, then return the user object and bam, login works.
Im not sure if im doing it the right way, cos pretty much flying by the seat of my pants. i saw examples where people used the token_loader, rather than the request_loader callback function, to load the token, but i couldnt figure out how to set & get the auth token to & from the client. maybe ill figure it out... one day...
if you have the same problem, maybe this might help? or just let me know what you think
cheers
I found this page when searching for help with Flask-Login + Flask-Dance. I was seeing current_user as AnonymousUserMixin in a handler with the #login_required decorator. In my case making sure #app.route is on the line above #login_required fixed the problem. The correct order is in the docs: https://flask-login.readthedocs.io/en/latest/#flask_login.login_required.

Categories

Resources