I am building a web application based on Python tornado and I am struggling with the authentication process. Based on the demos provided with tornado I create an authentication through Google.
I authenticated and get redirected to my index.
I try to connect to my /profile site, but tornado redirects me to Google Authentication and to my index.
/profile requires that the user is authenticated. In my case, tornado doesn't seem to
accept it or I made a mistake in the Authentication classes.
Why I cannot access my /profile site? What I am doing wrong with the authentication?
Authentication related handlers
from site import models
import mongoengine
import tornado.auth
import tornado.escape
import tornado.ioloop
import tornado.web
from tornado import gen
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("mysite")
if not user_json: return None
return tornado.escape.json_decode(user_json)
class AuthGoogleLoginHandler(BaseHandler, tornado.auth.GoogleMixin):
#gen.coroutine
def get(self):
if self.get_argument("openid.mode", None):
user = yield self.get_authenticated_user()
self.set_secure_cookie("mysite",
tornado.escape.json_encode(user))
email = user.get('email')
try:
print 'trying to find the user'
usr = models.User.objects.get(email=email)
except mongoengine.DoesNotExist as e:
# there is no user with the wished email address
# let's create a new one.
new_user = models.User()
new_user.email = user.get('email')
new_user.first_name = user.get('first_name')
new_user.last_name = user.get('last_name')
new_user.locale = user.get('locale')
new_user.save()
self.redirect('/')
return
self.authenticate_redirect()
class AuthLogoutHandler(BaseHandler):
'''
Log the current user out.
'''
def get(self):
self.clear_cookie("mysite")
self.redirect('/')
Other handlers and main
from mysite import models
from mysite import auth
from tornado.options import options, define, parse_command_line
import django.core.handlers.wsgi
import tornado.httpserver
import tornado.ioloop
import tornado.web
import tornado.escape
import tornado.wsgi
import os
define('port', type=int, default=1234)
class ProfileHandler(tornado.web.RequestHandler):
#tornado.web.authenticated
def get(self):
join = lambda x, y, separator: separator.join([x, y])
self.render('profile.html', user=user, join=join)
class ProfileEditHandler(tornado.web.RequestHandler):
#tornado.web.authenticated
def get(self):
self.render('profile-edit.html', user=user)
#tornado.web.authenticated
def post(self):
first_name = self.get_argument('first_name')
last_name = self.get_argument('last_name')
city = self.get_argument('city')
state = self.get_argument('state')
country = self.get_argument('country')
# write to MongoDB
self.redirect('/profile')
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render('welcome.html')
def main():
template_path=os.path.join(os.path.abspath(os.path.dirname(__file__)),"templates")
static_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'static')
wsgi_app = tornado.wsgi.WSGIContainer(django.core.handlers.wsgi.WSGIHandler())
handlers = [('/profile', ProfileHandler),
('/profile-edit', ProfileEditHandler),
('/auth/login', auth.AuthGoogleLoginHandler),
('/auth/logout', auth.AuthLogoutHandler),
('/', IndexHandler),
('.*', tornado.web.FallbackHandler, dict(fallback=wsgi_app)),]
tornado_app = tornado.web.Application(handlers,
static_path=static_path,
template_path=template_path,
cookie_secret='some_secret',
login_url='/auth/login',
debug=True)
server = tornado.httpserver.HTTPServer(tornado_app)
server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
Finally I found a solution. When the get method of my ProfileHandler was called the #tornado.web.authenticated decorator checks if a user is logged in.
But who finds out if there is a logged in user or not? In the basic tornado functionality this is not implemented. You have to create a BaseHandler and all other handlers which
need this authentication information should subclass the BaseHandler.
After the BaseHandler was defined and subclassed - authentication worked perfectly!
To sum it up. If you get stuck in a vicious cycle of log in requests:
1) Create a BaseHandler and override get_current_user.
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
user_json = self.get_secure_cookie("heroe")
if not user_json: return None
return tornado.escape.json_decode(user_json)
2) Subclass BaseHandler with your common handler:
class ProfileHandler(BaseHandler)
3) Add the tornado decorator #tornado.web.authenticated to ensure authentication:
class ProfileHandler(auth.BaseHandler):
#tornado.web.authenticated
def get(self):
self.render('profile.html')
Related
'I use the route food_search to get data from api and return an array'
import os
from unittest import TestCase
from models import db, User
# BEFORE we import our app, let's set an environmental variable
# to use a different database for tests (we need to do this
# before we import our app, since that will have already
# connected to the database
os.environ['DATABASE_URL'] = "postgresql:///mealplan_test"
from app import app
class FoodSearchTestCase(TestCase):
def setUp(self):
self.app = app
self.client = self.app.test_client()
with self.app.app_context():
db.drop_all()
db.create_all()
# Create a test user and log them in
user = User(username='testuser', password='testpassword', email='testing#test.com', firstname='fname', lastname='lname')
db.session.add(user)
db.session.commit()
def tearDown(self):
with self.app.app_context():
db.drop_all()
def test_food_search(self):
with self.client as c:
response = c.get("/food_search?q=banana")
self.assertEqual(response.status_code, 302)
self.assertIn(b'banana', response.data)
AssertionError:response.status_code 302 != 200
AssertionError: b'banana' not found in b'\nRedirecting...\nRedirecting...\nYou should be redirected automatically to target URL: /login. If not click the link.'
I'd like to setUp with unittest module.
My Flask App is created using factory (create_app) uses Flask-Babel for i18n/
def create_app(config=None, app_name=None, blueprints=None):
# Create Flask App instance
app_name = app_name or __name__
app = Flask(app_name)
app.config.from_pyfile(config)
configure_hook(app)
configure_blueprints(app, blueprints)
configure_extensions(app)
configure_jinja_filters(app)
configure_logging(app)
configure_error_handlers(app)
configure_cli(app)
return app
create_app function calls configure_extensions(app) which is as follows:
def configure_extensions(app):
"""Initialize Flask Extensions."""
db.init_app(app)
babel.init_app(app)
csrf.init_app(app)
#babel.localeselector
def get_locale():
# If logged in, load user locale settings.
user = getattr(g, 'user', None)
if user is not None:
return user.locale
# Otherwise, choose the language from user browser.
return request.accept_languages.best_match(
app.config['BABEL_LANGUAGES'].keys())
#babel.timezoneselector
def get_timezone():
user = getattr(g, 'user', None)
if user is not None:
return user.timezone
It works fine when I run app, but I can't create a unittest properly because it asserts error like this:
File "C:\projects\rabiang\venv\lib\site-packages\flask_babel\__init__.py", line 127, in localeselector
'a localeselector function is already registered'
AssertionError: a localeselector function is already registered
Due to the message "a localeselector function is already registered", I thought that fact that my setUp method of unittest was invoked when each test method is called makes problem. Thus, I changed #classmethod setUpClass like this:
# -*- coding: utf-8 -*-
import unittest
from app import create_app, db
from app.blueprints.auth import auth
from app.blueprints.forum import forum
from app.blueprints.main import main
from app.blueprints.page import page
class BasicsTestCase(unittest.TestCase):
#classmethod
def setUpClass(cls):
blueprints = [main, page, auth, forum]
app = create_app(config='../test.cfg', blueprints=blueprints)
cls.app = app.test_client()
db.create_all()
#classmethod
def tearDownClass(cls):
db.session.remove()
db.drop_all()
def test_app_exists(self):
self.assertFalse(BasicsTestCase.app is None)
if __name__ == '__main__':
unittest.main()
However, #babel.localeselector and #babel.timezoneselector decorator doesn't work.
I fixed it by setting the app only once with the function setUpClass from unittest.
See also the answer Run setUp only once
I'm trying to setup unittests for my App Engine tutorial example.
I have defined the user in self.testbed.setup_env but the data is not visible in the tested code. Somehow, despite this data not being rendered by the print statement, it's being saved as a structured property in the datastore and is not deleted in the TearDown().
The code works when I use dev_appserver.py and enter the data into the form manually.
When I attempt to run the tests with nosetests --with-gae, the mock user data is not not available
Since I'm attempting to debug a test, I can't use a debugger and I'm relying on print statements.
import sys, os, subprocess, time, unittest, shlex
sys.path.append("/usr/local/google_appengine")
sys.path.append('/usr/local/google_appengine/lib/')
sys.path.append("/usr/local/google_appengine/lib/yaml/lib")
sys.path.append("/usr/local/google_appengine/lib/webapp2-2.5.2")
sys.path.append("/usr/local/google_appengine/lib/django-1.5")
sys.path.append("/usr/local/google_appengine/lib/cherrypy")
sys.path.append("/usr/local/google_appengine/lib/concurrent")
sys.path.append("/usr/local/google_appengine/lib/docker")
sys.path.append("/usr/local/google_appengine/lib/requests")
sys.path.append("/usr/local/google_appengine/lib/websocket")
sys.path.append("/usr/local/google_appengine/lib/fancy_urllib")
sys.path.append("/usr/local/google_appengine/lib/antlr3")
os.environ['APPLICATION_ID'] = 'myapp'
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from google.appengine.api import memcache, apiproxy_stub, apiproxy_stub_map
from google.appengine.ext import testbed
from google.appengine.datastore import datastore_stub_util
from google.appengine.tools.devappserver2 import devappserver2
from guestbook import Author, Greeting
from google.appengine.api import users
class NewVisitorTest(unittest.TestCase):
# enable the datastore stub
nosegae_datastore_v3 = True
nosegae_datastore_v3_kwargs = {
'datastore_file': '/tmp/nosegae.sqlite3',
'use_sqlite': True
}
def setUp(self):
# Start the dev server
cmd = "/usr/local/bin/dev_appserver.py /Users/Bryan/work/GoogleAppEngine/guestbook/app.yaml --port 8080 --storage_path /tmp/datastore --clear_datastore --skip_sdk_update_check"
self.dev_appserver = subprocess.Popen(shlex.split(cmd),
stdout=subprocess.PIPE)
time.sleep(2) # Important, let dev_appserver start up
self.testbed = testbed.Testbed()
self.testbed.setup_env(app_id='guestbook')
self.testbed.activate()
self.datastore_stub = apiproxy_stub_map.apiproxy.GetStub('datastore_v3')
# setup the dev_appserver
APP_CONFIGS = ['app.yaml']
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
#
def tearDown(self):
self.testbed.deactivate()
self.browser.quit()
self.dev_appserver.terminate()
def loginUser(self, email="elonMusk#example.com", id='123456', is_admin=False):
self.testbed.setup_env(
user_email=email,
user_id=id,
user_is_admin='1' if is_admin else '0',
overwrite=True
)
self.testbed.init_user_stub()
def test_guest_can_submit_new_greeting_and_author(self):
self.browser.get('http://localhost:8080')
self.loginUser()
self.browser.find_element_by_name('content').send_keys("Test Driven Development is awesome!!!")
self.browser.find_element_by_id('submit_guestbook').submit()
# Can I access them via querys on the database?
assert("Test Driven Development is awesome!!!" in self.browser.page_source)
self.browser.get('http://localhost:8000/datastore')
assert(Greeting.query(Greeting.author.email=='elonMusk#example.com').get())
self.browser.get('http://localhost:8000/datastore')
# I'm opening the datastore to see that the entity is saved.
# The entity is saved but the Author property is empty.
time.sleep(10) # this gives me time to view the datastore in browser.
self.assertEqual(1, Greeting.query().count())
Here is questbook.py:
import os
import urllib
from google.appengine.api import users
from google.appengine.ext import ndb
import jinja2
import webapp2
JINJA_ENVIRONMENT = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
extensions=['jinja2.ext.autoescape'],
autoescape=True)
DEFAULT_GUESTBOOK_NAME = 'default_guestbook'
# We set a parent key on the 'Greetings' to ensure that they are all
# in the same entity group. Queries across the single entity group
# will be consistent. However, the write rate should be limited to
# ~1/second.
def guestbook_key(guestbook_name=DEFAULT_GUESTBOOK_NAME):
"""Constructs a Datastore key for a Guestbook entity.
We use guestbook_name as the key.
"""
return ndb.Key('Guestbook', guestbook_name)
class Author(ndb.Model):
"""Sub model for representing an author."""
identity = ndb.StringProperty(indexed=False)
email = ndb.StringProperty(indexed=True)
class Greeting(ndb.Model):
"""A main model for representing an individual Guestbook entry."""
author = ndb.StructuredProperty(Author)
content = ndb.StringProperty(indexed=False)
date = ndb.DateTimeProperty(auto_now_add=True)
class MainPage(webapp2.RequestHandler):
def get(self):
guestbook_name = self.request.get('guestbook_name',
DEFAULT_GUESTBOOK_NAME)
greetings_query = Greeting.query(
ancestor=guestbook_key(guestbook_name)).order(-Greeting.date)
greetings = greetings_query.fetch(10)
user = users.get_current_user()
if user:
url = users.create_logout_url(self.request.uri)
url_linktext = 'Logout'
else:
url = users.create_login_url(self.request.uri)
url_linktext = 'Login'
template_values = {
'user': user,
'greetings': greetings,
'guestbook_name': urllib.quote_plus(guestbook_name),
'url': url,
'url_linktext': url_linktext,
}
template = JINJA_ENVIRONMENT.get_template('index.html')
self.response.write(template.render(template_values))
class Guestbook(webapp2.RequestHandler):
def post(self):
guestbook_name = self.request.get('guestbook_name',
DEFAULT_GUESTBOOK_NAME)
greeting = Greeting(parent=guestbook_key(guestbook_name))
if users.get_current_user():
greeting.author = Author(
identity=users.get_current_user().user_id(),
email=users.get_current_user().email())
greeting.content = self.request.get('content')
######################################
import os
print("XXXXXXXXXXXXXXXXXXXXX guestbook in post() USER_EMAIL: %s" % os.environ.get('USER_EMAIL'))
#print("xoxoxoxoxoxoxoxoxoxox guestbook greeting.author.email: %s" % greeting.author.email )
greeting_key = greeting.put()
query_params = {'guestbook_name': guestbook_name}
self.redirect('/?' + urllib.urlencode(query_params))
application = webapp2.WSGIApplication([
('/', MainPage),
('/sign', Guestbook),
], debug=False)
The datastore you can access from your test suite (the datastore_stub "mock" datastore) is not the same as the one the dev_appserver uses in your example. Hence changes to one of two datastores (such as adding an entity) will not be reflected in the other one, which is why your Greeting.query() returns nothing.
Instead of running a full blown, separate dev_appserver process, you can write your tests using the webtest framework (http://docs.pylonsproject.org/projects/webtest/en/latest/testapp.html) which will be simpler than having to manage a separate process; it will look this:
def setUp(self):
[...]
self.web_app = webtest.TestApp(guestbook.application)
[...]
def test_get(self):
response = self.web_app.get('/')
Essentially, I have a directory as such:
/app
runserver.py
/myapp
__init__.py
api.py
auth.py
/resources
__init.py
users.py
login.py
/models
__init.py
models.py
/common
/assets
In my auth.py I have a standard HTTP-basic username/password authentication. I will use these for areas where login is a must, and I want to verify each user. Login.py is where I need to add my decorator, but the whole app does not run due to this error: AttributeError: 'module' object has no attribute 'login_required'
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 or not user.verify_password(password):
return False
g.user = user
return True
#auth.error_handler
def unauthorized():
return make_response(jsonify({'message': 'Unauthorized'}), 403)
My code for the login.py, which calls the decorator and then asks for the auth.
from flask_restful import Resource, reqparse
from myapp.models.users import User
from myapp import auth
class login(Resource):
decorators = [auth.login_required]
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('userid', type = str , default="")
self.reqparse.add_argument('username', type = str, default="")
self.reqparse.add_argument('password', type = str, default="")
super(login, self).__init__()
def post(self):
args = self.reqparse.parse_args()
username = args['username']
password = args['password']
message = {'status': 'Authorized'}
return message
So to wrap it up, my question is: How and where do I add the flask-httpauth class so I can use the decorators. My option right now may be to paste that auth code in every resource class that needs it, but there seems there must be a better way to organize that. Help?
You are importing your auth module when really you want to be importing the HTTPBasicAuth object in that module. It is also possible you're running in to problems due to the fact that your module has the same name as the HTTPBasicAuth object.
I recommend renaming your auth.py to something else, such as authentication.py, and change your import to:
from ..authentication import auth
This gets a bit confusing because you have an auth.py module that defines an auth variable inside.
The line:
from myapp import auth
is importing the module, not the variable defined in it. Change it to:
from myapp.auth import auth
And I think that will work.
Sorry this is a bit old, but for the sake of others with this question, I would suggest not using flask.ext.httpauth. I found it isn't very useful. Here is how I do my HTTP basic auth with flask-restful.
This is in the myapp/init.py:
from flask import Flask, request
from flask.ext.restful import abort
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth:
abort(401)
user = User.query.filter(User.username == auth.username).first()
auth_ok = False
if user != None:
auth_ok = verify_password(auth.password) == user.password
if not auth_ok:
return abort(401)
return f(*args, **kwargs)
return decorated
Resource script that has a resource that requires authorization to access the resource.
from myapp import requires_auth
#requires_auth
def get(self):
# do something
I'm a newbie to app engine and python and am just trying to get a basic idea for how things work.
I have a simple app, with one mapped url (/). All the classes I'm trying to use are in the base directory of the app.
This is my main.py - all i want to do is use the middleware class to pass a variable to the template so I can render different parts of the page depending on device type.
import webapp2
import jinja2
import os
from useragents import search_strings
jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
class MainPage(webapp2.RequestHandler):
def get(self):
template = jinja_environment.get_template('templates/index.html')
self.response.out.write(template.render())
app = webapp2.WSGIApplication([('/', MainPage)],
debug=True)
class Middleware(object):
#staticmethod
def process_request(request):
"""Adds a "mobile" attribute to the request which is True or False
depending on whether the request should be considered to come from a
small-screen device such as a phone or a PDA
//rest of class is [here][1]
"""
import webapp2
import jinja2
import os
from useragents import search_strings
jinja_environment = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.dirname(__file__)))
class MainPage(webapp2.RequestHandler):
def get(self):
#i don't know if you want to overwrite self.request but here it is
self.request = Middleware.process_request(self.request)
template = jinja_environment.get_template('templates/index.html')
self.response.out.write(template.render())
app = webapp2.WSGIApplication([('/', MainPage)],
debug=True)
class Middleware(object):
#staticmethod
def process_request(request):
"""Adds a "mobile" attribute to the request which is True or False
depending on whether the request should be considered to come from a
small-screen device such as a phone or a PDA
//rest of class is [here][1]
"""