Tornado Auth (Twittermixin) issue - python

I am currently trying to use tornado to display my twitter streams. Below is my code:
#!/usr/bin/env python
import time
import logging
from tornado.auth import TwitterMixin
from tornado.escape import json_decode, json_encode
from tornado.ioloop import IOLoop
from tornado import gen
from tornado.options import define, options, parse_command_line, parse_config_file
from tornado.web import Application, RequestHandler, authenticated, HTTPError
define('port', default=8080, help="port to listen on")
define('config_file', default='secrets.cfg',
help='filename for additional configuration')
define('debug', default=True, group='application',
help="run in debug mode (with automatic reloading)")
# The following settings should probably be defined in secrets.cfg
define('twitter_consumer_key', type=str, group='application')
define('twitter_consumer_secret', type=str, group='application')
define('cookie_secret', type=str, group='application',
default='this is a string',
help="signing key for secure cookies")
class BaseHandler(RequestHandler):
COOKIE_NAME = "uuser"
def get_current_user(self):
user_json = self.get_secure_cookie(self.COOKIE_NAME)
if not user_json:
print(" No user_json")
return None
print(" Yes user_json")
return json_decode(user_json)
class MainHandler(BaseHandler, TwitterMixin):
#authenticated
#gen.coroutine
def get(self):
timeline = yield self.twitter_request(
'/statuses/home_timeline',
access_token = self.current_user['access_token'])
self.render('home.html', timeline=timeline)
class LoginHandler(BaseHandler, TwitterMixin):
#gen.coroutine
def get(self):
if self.get_argument('oauth_token', None):
user = yield self.get_authenticated_user()
print(' user:', type(user))
del user["description"]
self.set_secure_cookie(self.COOKIE_NAME, json_encode(user))
print(' get_secure_cookie:', self.get_secure_cookie(self.COOKIE_NAME) )
self.redirect(self.get_argument('next', '/'))
else:
print(" Authorize_redirecting...")
yield self.authorize_redirect(callback_uri=self.request.full_url())
class LogoutHandler(BaseHandler):
def get(self):
self.clear_cookie("user")
def main():
parse_command_line(final=False)
parse_config_file(options.config_file)
app = Application(
[
(r'/', MainHandler),
(r'/login', LoginHandler),
(r'/logout', LogoutHandler),
],
login_url='/login',
**options.group_dict('application'))
app.listen(options.port)
logging.info('Listening on http://localhost:%d' % options.port)
IOLoop.current().start()
if __name__ == '__main__':
main()
So my understanding of the flow is as follows:
1.) Visit '/' - MainHandler, the #authenticated will redirect to login_url if the user is not logged in.
2.) Visit '/login' - LoginHandler, self.authorize_redirect(callback_uri=self.request.full_url()) will append oauth_token argument at the end of url, and re-visit '/login'
3.) Visit'/login' - LoginHandler, obtain user from self.get_authenticated_user(), and set_secure_cookie(self.COOKIE_NAME, json_encode(user))
And here is the problem I think, I can't seem to set the cookie. When I try to access it immediately by self.get_secure_cookie(self.COOKIE_NAME), it returns None, and hence it keeps on re-visiting '/login'
Can anybody offer some help to my problem? Maybe it is something very obvious I am not seeing. Thanks
I have also set http://127.0.0.1:8080/ as the callback url on my twitter app setting, not sure if this has any contribution to the problem.

Final solution!!
#!/usr/bin/env python
import time
import uuid
import logging
from tornado.auth import TwitterMixin
from tornado.escape import json_decode, json_encode, url_escape, url_unescape
from tornado.ioloop import IOLoop
from tornado import gen
from tornado.options import define, options, parse_command_line, parse_config_file
from tornado.web import Application, RequestHandler, authenticated, HTTPError
from urllib.parse import quote
import re
define('port', default=8080, help="port to listen on")
define('config_file', default='secrets.cfg',
help='filename for additional configuration')
define('debug', default=True, group='application',
help="run in debug mode (with automatic reloading)")
# The following settings should probably be defined in secrets.cfg
define('twitter_consumer_key', type=str, group='application')
define('twitter_consumer_secret', type=str, group='application')
# define('cookie_secret', type=str, group='application',
# default='thisisastring',
# help="signing key for secure cookies")
class BaseHandler(RequestHandler):
COOKIE_NAME = "user"
def get_current_user(self):
user_json = self.get_cookie(self.COOKIE_NAME)
if not user_json:
print("\n - Cannot obtain cookie from client browser")
return None
print("\n - Cookie obtained from client browser")
return json_decode(user_json)
class MainHandler(BaseHandler, TwitterMixin):
#authenticated
#gen.coroutine
def get(self):
print("\n - Obtaining timeline from twitter")
timeline = yield self.twitter_request(
'/statuses/home_timeline',
access_token = self.current_user)
self.render('home.html', timeline=timeline)
class LoginHandler(BaseHandler, TwitterMixin):
#gen.coroutine
def get(self):
if self.get_argument('oauth_token', None):
print("\n - Authenticating with oauth_token...")
user = yield self.get_authenticated_user()
encoded_token = json_encode(user['access_token'])
# remove certain ascii symbols which are rejected
# by self.set_cookie() function...
encoded_token = re.sub(r"[\x00-\x20]", '', encoded_token)
# save encoded token as cookie
self.set_cookie(name=self.COOKIE_NAME, value=encoded_token)
self.redirect(self.get_argument('next', '/'))
else:
print("\n - Authorize_redirecting...")
yield self.authorize_redirect(callback_uri=self.request.full_url())
class LogoutHandler(BaseHandler):
def get(self):
self.clear_cookie(self.COOKIE_NAME)
def main():
parse_command_line(final=False)
parse_config_file(options.config_file)
app = Application(
[
(r'/', MainHandler),
(r'/login', LoginHandler),
(r'/logout', LogoutHandler),
],
login_url='/login',
cookie_secret=str(uuid.uuid4().bytes),
**options.group_dict('application'))
app.listen(options.port)
logging.info('Listening on http://localhost:%d' % options.port)
IOLoop.current().start()
if __name__ == '__main__':
main()

Related

Tornado Framework - how to use Handlersrequest to call an asynchronous function

This is a Python code for a Tornado web application. The code defines two classes, BaseHandler and MainHandlerIndex, both of which inherit from the Tornado web library's RequestHandler. The BaseHandler class has a method, "get_username_id_get_data", which returns the username ID obtained from a "get_data" module. The MainHandlerIndex class has a "get" method decorated with the "#decoradores.authenticated" and "#gen.coroutine" decorators, which handles incoming GET requests. This method retrieves the username cookie, obtains the username ID using the "get_username_id_get_data" method, and then renders an HTML template "index/index.html" with the title, items, username, and version as variables.
import tornado.web
from tornado import gen
from functools import wraps
import modulos.decoradores as decoradores
import modulos.get_data as get_data
import logging
logging.basicConfig(level=logging.DEBUG)
import tracemalloc; tracemalloc.start()
class BaseHandler(tornado.web.RequestHandler):
async def get_username_id_get_data(self):
print("soy basehandler y estoy siendo ejecutado....")
username_id = await get_data.primero_redis_segundo_query("user", "('username', '=', 'test')", 'x')
print("get_username_id_get_data: ", username_id)
return username_id
class MainHandlerIndex(BaseHandler):
#decoradores.authenticated
#gen.coroutine
async def get(self):
print("Iniciando MainHandlerIndex metodo: GET")
username_cookie_pre = self.get_secure_cookie("user")
username_cookie = username_cookie_pre.decode()
#username_cookie = 'test'
username_id = await self.get_username_id_get_data()
version = self.application.settings['valores_entorno']['version']
print("###########################################################################")
print("00_username_cookie------------>",username_cookie )
print("11_username_id------------>",username_id )
print("22_version------------>",version )
print("###########################################################################")
items_test = ["Item 1_index", "Item 2_index", "Item 3_index"]
#items_test = []
self.render("index/index.html", title="test", items=items_test, username=str(username_cookie).strip("\""), version=version)
This is the contents of the file index.py:
import asyncio
import os
import tornado.web
#----------------------------#
# Definir entorno #
#----------------------------#
from config import Settings, PreProductionSettings, ProductionSettings
import tornado.options
tornado.options.parse_config_file('config.py')
valores_entorno = PreProductionSettings()
#print(tornado.options.options.version)
#----------------------------#
# Handlers from modules #
#----------------------------#
from blueprints.test.handlers import MainHandlerTest #TEST <-- Borrar....from blueprints.index.handlers import MainHandlerIndex #Index
from blueprints.rid_propios.ri_handlers import MainHandlerRid_Propio_Index #Remote_id_propios
from blueprints.login.login_handlers import MainHandlerLogin, LogoutHandler, RegisterHandler, ConfirmeHandler
from blueprints.index.handlers import MainHandlerIndex
#----------------------------#
# Módulos del proyecto #
#----------------------------#
import modulos.gestiondb as gestiondb
import modulos.redis_stack as redisstack
import modulos.tablas as Tablas
from aiopg.sa import create_engine
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
print("BASE_DIR: ", BASE_DIR)
engine = create_engine(
user=tornado.options.options.user,
database=tornado.options.options.database,
host=tornado.options.options.host,
password=tornado.options.options.password,
)
def make_app():
settings = {
"template_path": os.path.join(os.path.dirname(__file__), "templates"), #/home/developer/xrig_torado/web/web/blueprints/base/templates
"blueprints_path": os.path.join(os.path.dirname(__file__), "web/blueprints"), #/home/developer/xrig_torado/web/web/blueprints
"static_path": os.path.join(os.path.dirname(__file__), "static"), #/home/developer/xrig_torado/web/static
"cookie_secret": tornado.options.options.cookie_secret,
"login_url": "/login",
"xsrf_cookies": True,
"valores_entorno": tornado.options.options
}
handlers = (
(r'/', MainHandlerIndex),
(r'/login', MainHandlerLogin),
(r'/logout', LogoutHandler),
(r'/register', RegisterHandler),
(r'/confirmar', ConfirmeHandler),
(r'/test', MainHandlerTest),
(r'/rid_propios_index', MainHandlerRid_Propio_Index)
)
return tornado.web.Application(
handlers,
debug = True,
autoreload = True,
**settings,
#db = db
db_engine = engine,
)
async def main():
app = make_app()
app.listen(tornado.options.options.puerto_xrig)
#await asyncio.Event().wait()
await asyncio.Event().wait()
print(f'🌐 Server is listening on localhost on port {tornado.options.options.puerto_xrig}')
#Comprobando que existen la tablas del proyecto:
db_dsn = tornado.options.options.dsn
#lista_tablas = [gestiondb.tbl_benchmark, gestiondb.alpha, gestiondb.user, ]
lista_tablas = [Tablas.tbl_benchmark, Tablas.alpha, Tablas.user, Tablas.rid, Tablas.rid_propio ]
await gestiondb.rutina_alta_db_proyecto(db_dsn, lista_tablas)
#Comprobaciones Redis Stack.
await redisstack.lista_tablas_para_volcar_en_redis()
shutdown_event = asyncio.Event()
await shutdown_event.wait()
if __name__ == "__main__":
asyncio.run(main())
The extension starts correctly, does the login process and once the authenticated user is when I make a redirect to the root path / (this is where I can not operate a code that can call asynchronous functions). I have tried to follow several examples and there is no way the error is always:
http://http://192.168.196.49:8888/ (depués del proceso login)
[I 230205 19:48:57 web:2271] 200 GET / (192.168.196.4) 14.50ms
/usr/lib/python3.9/asyncio/events.py:80: RuntimeWarning: coroutine 'MainHandlerIndex.get' was never awaited
self._context.run(self._callback, *self._args)
Object allocated at (most recent call last):
File "/home/developer/xrig_torado/lib/python3.9/site-packages/tornado/gen.py", lineno 216
result = ctx_run(func, *args, **kwargs)
[W 230205 19:48:57 web:2271] 404 GET /favicon.ico (192.168.196.4) 7.46ms
I do NOT find enough documentation that explains an architecture to do this kind of thing. The intention is in the requestHandler in the GET or POST method to develop code and be able to call asynchronous functions.

How to send immediate GET reponse in tornado?

I have the following code to send result to browser based on the api call.
import tornado.ioloop
import tornado.web
from tornado import gen
from datetime import date
class GetGameByIdHandler(tornado.web.RequestHandler):
#gen.coroutine
def get(self, id):
response = { 'id': int(id),
'name': 'Crazy Game',
'release_date': date.today().isoformat() }
self.set_header('Content-Type', 'text/json')
self.write(response)
for i in range(10000000):
for j in range(10):
pass
print i
application = tornado.web.Application([
(r"/getgamebyid/([0-9]+)", GetGameByIdHandler),
], debug = True)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
I want that the api should return result as soon as self.write is encountered. The for loop should be run after that. How can I get this done? Basically, I don't want to return the result immediately.
NOTE: The loop here has no real purpose except to demonstrate the sending of result is delayed just because of this extra thing in the get function.
A less abstract example:
import tornado.ioloop
import tornado.web
from tornado import gen
from datetime import date
class GetGameByIdHandler(tornado.web.RequestHandler):
#gen.coroutine
def get(self, id):
result_dict = GetResultsFromDB(id)
response = result_dict
self.set_header('Content-Type', 'text/json')
self.write(response)
# Basically i want to doSomething basedon results
# Generated from DB
for key in result_dict:
if result_dict[key] == None:
DoSomething()
application = tornado.web.Application([
(r"/getgamebyid/([0-9]+)", GetGameByIdHandler),
], debug = True)
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
if you need to run some code after writing all data to a socket, you can use tornado.web.RequestHandler.flush:
self.write(response)
self.flush(callback=lambda: DoSomethingWrapper(response))

self.async_callback missing from classes that inherit from RequestHandler

So I'm learning the Tornado web framework right now, and following a few examples from a book I managed to get authentication with OAuth2/Google almost working. It redirects to Google to prompt a user to sign in, but after signing in it throws the following error at me:
Traceback (most recent call last):
File "C:\python27\lib\site-packages\tornado\web.py", line 1288, in _stack_context_handle_exception
raise_exc_info((type, value, traceback))
File "C:\python27\lib\site-packages\tornado\web.py", line 1475, in wrapper
result = method(self, *args, **kwargs)
File "C:\Users\enricojr\Downloads\Github\LearningTornado\grumble_login.py", line 8, in get
self.get_authenticated_user(callback=self.async_callback(self._on_auth))
AttributeError: 'LoginHandler' object has no attribute 'async_callback'
Upon checking the Tornado source code on Github, it does appear indeed that the async_callback() method is gone. Any idea on how to handle this? Every example of authentication using Tornado's auth module I've seen thus far calls for it to be used, and since I'm new to the whole 'asynchronous web programming' thing I'm not quite sure at this point how else I can do it.
My code is below, Python 2.7 and Tornado 4.0.2:
############################
# grumble_login.py
############################
from tornado.auth import GoogleMixin
from tornado.web import RequestHandler, asynchronous
class LoginHandler(RequestHandler, GoogleMixin):
#asynchronous
def get(self):
if self.get_argument("openid.mode", None):
self.get_authenticated_user(callback=self.async_callback(self._on_auth))
return
else:
self.authenticate_redirect()
def _on_auth(self, user):
if not user:
self.clear_all_cookies()
raise tornado.web.HTTPError(500, "Google auth failed.")
else:
# the user id serves as the basis for
# all the data we store.
self.set_secure_cookie('user_id', user['id'])
self.redirect("/")
class LogoutHandler(RequestHandler):
def get(self):
self.clear_all_cookies()
self.render("logout.html")
############################
# main.py
############################
import os
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application
from tornado.options import define, options
from grumble_handlers import *
from grumble_user_handlers import *
from grumble_login import *
# /user/profile/<user> - leads to profile page
# /user/posts - leads to posts made by user
# /post/<id> - leads to a specific post
# /post/add - add post
# /post/edit - edit post
# /post/delete - delete post
# D$oP5lz3D$oP5lz3
TEMPLATE_PATH = os.path.join(os.path.dirname(__file__), "templates")
STATIC_PATH = os.path.join(os.path.dirname(__file__), "static")
if __name__ == "__main__":
define("port", default=8000, help="run on the given port", type=int)
settings_dict = {
'cookie_secret': "GENERATE NEW KEY HERE",
'template_path': TEMPLATE_PATH,
'static_path': STATIC_PATH,
'debug': True,
'login_url': "/login",
}
general_handlers = [
(r"/", IndexHandler),
(r"/login", LoginHandler),
(r"/logout", LogoutHandler)
]
post_handler_list =[
(r"/post/(\d+)", PostFetchHandler), # leads to a single post page
(r"/post/add", PostAddHandler),
(r"/post/edit", PostEditHandler),
(r"/post/delete", PostDeleteHandler),
]
user_handler_list = [
(r"/user/(\d+)/posts", UserPostsHandler), # lists out all the posts by a given user
(r"/user/(\d+)/profile", UserProfileHandler),
]
master_handler_list = general_handlers + post_handler_list + user_handler_list
app = Application(
handlers=master_handler_list,
**settings_dict
)
http_server = HTTPServer(app)
http_server.listen(options.port)
# GO TEAM GO
IOLoop.instance().start()
############################
# grumble_handlers.py
############################
...
class IndexHandler(NotImplementedHandler):
#authenticated
def get(self):
pass
The book is, unfortunately, long outdated. async_callback was removed in Tornado 4 since it is no longer required. You can simply do:
self.get_authenticated_user(callback=self._on_auth)
Once you have that working, I recommend following the coroutine documents and learn how to use "yield":
user = yield self.get_authenticated_user()

How to log redirects in Pyramid?

I'm trying to set it up so that my Pyramid app logs a message whenever it does a redirect - e.g. When one of my views raises HTTPFound.
Creating a custom subclass of HTTPFound worked but it sucks to have to make sure that class is used everywhere in the app.
I then had the idea of creating a custom exception view with context=HTTPFound but that view doesn't seem to get called.
Is there a standard way to set up special handling for a redirect that is global to the app?
To log out redirects you would just have a tween that checks the return status, something like:
from wsgiref.simple_server import make_server
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPFound
def hello1(request):
raise HTTPFound(request.route_url('hello2'))
def hello2(request):
return {'boom': 'shaka'}
def redirect_logger(handler, registry):
def redirect_handler(request):
response = handler(request)
if response.status_int == 302:
print("OMGZ ITS NOT GOING WHERE YOU THINK")
return response
return redirect_handler
def main():
config = Configurator()
config.add_route('hello1', '/', view=hello1)
config.add_route('hello2', '/hi', view=hello2, renderer='json')
config.add_tween('__main__.redirect_logger')
app = config.make_wsgi_app()
return app
if __name__ == '__main__':
app = main()
server = make_server('0.0.0.0', 8080, app)
print("http://0.0.0.0:8080")
server.serve_forever()

How do I handle logouts with repoze.who (and bottle.py)?

I'm trying to make bottle.py work with repoze.who, and so far have managed to put together the following very simplistic program to get it working, using a combination of various examples I've found. Obviously this isn't something I'd run in production, I'm just trying to make the least complicated code I can so that I can learn how to use this - but unfortunately, tutorials for using bottle.py with repoze.who are very few and far between.
This example below works, and allows someone to login with username/password of admin/admin. What am I supposed to do with repoze.who to make the logout() function work? I gather there's a forget function that might be for this purpose, but I can't work out how I'm meant to call it.
Thanks.
from bottle import route, run, app, get, abort, request
from StringIO import StringIO
import repoze
from repoze.who.middleware import PluggableAuthenticationMiddleware
from repoze.who.interfaces import IIdentifier
from repoze.who.interfaces import IChallenger
from repoze.who.plugins.basicauth import BasicAuthPlugin
from repoze.who.plugins.auth_tkt import AuthTktCookiePlugin
from repoze.who.plugins.cookie import InsecureCookiePlugin
from repoze.who.plugins.form import FormPlugin
from repoze.who.plugins.htpasswd import HTPasswdPlugin
from repoze.who.classifiers import default_request_classifier
from repoze.who.classifiers import default_challenge_decider
import logging, sys
import pprint
#route('/')
def root():
if request.environ.get('repoze.who.identity') is None:
abort(401, "Not authenticated")
return "Authenticated"
#route('/hello')
def index():
identity = request.environ.get('repoze.who.identity')
if identity == None:
abort(401, "Not authenticated")
user = identity.get('repoze.who.userid')
return '<b>Hello %s!</b>' % user
#route('/logout')
def logout():
# I have no idea what to put here
pass
io = StringIO()
salt = 'aa'
for name, password in [ ('admin', 'admin'), ('paul', 'paul') ]:
io.write('%s:%s\n' % (name, password))
io.seek(0)
def cleartext_check(password, hashed):
return password == hashed
htpasswd = HTPasswdPlugin(io, cleartext_check)
basicauth = BasicAuthPlugin('repoze.who')
auth_tkt = AuthTktCookiePlugin('secret', 'auth_tkt')
form = FormPlugin('__do_login', rememberer_name='auth_tkt')
form.classifications = { IIdentifier:['browser'],
IChallenger:['browser'] }
identifiers = [('form', form),('auth_tkt',auth_tkt),('basicauth',basicauth)]
authenticators = [('htpasswd', htpasswd)]
challengers = [('form',form), ('basicauth',basicauth)]
mdproviders = []
log_stream = None
import os
if os.environ.get('WHO_LOG'):
log_stream = sys.stdout
middleware = PluggableAuthenticationMiddleware(
app(),
identifiers,
authenticators,
challengers,
mdproviders,
default_request_classifier,
default_challenge_decider,
log_stream = log_stream,
log_level = logging.DEBUG
)
if __name__ == '__main__':
run(app=middleware, host='0.0.0.0', port=8080, reloader=True)
else:
application = middleware
run(host='0.0.0.0', port=8080)
If you can I would use RedirectingFormPlugin rather than FormPlugin. RedirectingFormPlugin allows you to register a logout URL. With it you do not have to implement the /logout handler as such as the RedirectingFormPlugin intercepts the request and handles the calls to forget etc. for you. I have used this with Bobo and appengine and it works well.
if you still want to do it the unpreferred way in old repoze.who v1, following worked for me:
from bottle import response # , redirect
# ...
#route('/logout')
def logout():
identity = request.environ.get('repoze.who.identity')
if identity:
for (i_name, i) in identifiers:
hdrs = i.forget(request.environ, identity)
[ response.add_header(*h) for h in hdrs ]
## following would be nice, but does not work,
## since redirect is not using defined response headers
# rfr = request.get_header('referer', '/')
# redirect(rfr)
## so we do just this:
return "you have been hopefully logged out"

Categories

Resources