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

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"

Related

Testing a POST method unit test which inserts data to mongodb database

I want to know how am I supposed to test my code and see whether it works properly. I want to make sure that it stores the received data to the database. Can you please tell me how am I supposed to do that? While I was searching the forum I found this post but I did not really understand what is going on. here is the code I want to test.
client = MongoClient(os.environ.get("MONGODB_URI"))
app.db = client.securify
app.secret_key = str(os.environ.get("APP_SECRET"))
#app.route("/", methods=["GET", "POST"])
def home():
if request.method == "POST":
ip_address = request.remote_addr
entry_content = request.form.get("content")
formatted_date = datetime.datetime.today().strftime("%Y-%m-%d/%H:%M")
app.db.entries.insert({"content": entry_content, "date": formatted_date, "IP": ip_address})
return render_template("home.html")
and here is the mock test I wrote:
import os
from unittest import TestCase
from app import app
class AppTest(TestCase):
# executed prior to each test
def setUp(self):
# you can change your application configuration
app.config['TESTING'] = True
# you can recover a "test cient" of your defined application
self.app = app.test_client()
# then in your test method you can use self.app.[get, post, etc.] to make the request
def test_home(self):
url_path = '/'
response = self.app.get(url_path)
self.assertEqual(response.status_code, 200)
def test_post(self):
url_path = '/'
response = self.app.post(url_path,data={"content": "this is a test"})
self.assertEqual(response.status_code, 200)
The test_post gets stuck and after some seconds gives an error when reaches app.db.entries.insert({"content": entry_content, "date": formatted_date, "IP": ip_address}) part. Please tell me also how can I retrieve the saved data in order to make sure it is saved in the expected way
This is what I do using NodeJS, not tested at all in python but the idea is the same.
First of all, find a in-memory DB, there are options like pymongo-inmemory or mongomock
Then in your code you have to do the connection according to you environment (production/development/whatever)
Something like this:
env = os.environ.get("ENV")
if env == "TESTING":
# connect to mock db
elif env == "DEVELOMPENT":
# for example if you want to test against a real DB but not the production one
# then do the connection here
else:
# connect to production DB
I don't know if it is the proper way to do it but I found a solution. After creating a test client self.app = app.test_client() the db gets set to localhost:27017 so I changed it manually as follows and it worked:
self.app = app.test_client()
client = MongoClient(os.environ.get("MONGODB_URI"))

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()

Tornado Not Interpreting URL

I've been trying to create a web service using Tornado Framework. The system is supposed to handle URLs such as the following:
IP:Port/?user=1822&catid=48&skus=AB1,FS35S,98KSU1
First I've created this code to read urls:
#!/usr/bin/env python
from datetime import date
import tornado.httpserver
import tornado.escape
import tornado.ioloop
import tornado.web
class WService(tornado.web.RequestHandler):
def get(self, url):
self.write("value of url: %s" %(url))
application = tornado.web.Application([
(r"/([^/]+)", WService)])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(9000)
tornado.ioloop.IOLoop.instance().start()
and entering the url:
IP:Port/hello_world
resulted in:
value of url: hello_world
Any character used in the URL works, except for the "?". When changing the code such as:
application = tornado.web.Application([
(r"/?([^/]+)", WService)])
and sent the url with the "?" mark (IP:Port/?hello_world) the result is:
404: Not Found
Researching about Tornado to solve this problem I found the get_argument method and tried to apply it, such as:
class WService2(tornado.web.RequestHandler):
def get(self):
user = self.get_argument('user', None)
respose = { 'user': user }
self.write(response)
and
application = tornado.web.Application([
(r"/", WService2),
])
But sending the URL IP:Port/user=5 returned:
404: Not Found
I also tried:
application = tornado.web.Application([
(r"/(\w+)", WService2),
])
And also:
application = tornado.web.Application([
(r"/([^/]+)", WService2),
])
and nothing worked.
Am I doing something wrong to read the URLs with the "?" marks and is there a reason why Tornado is not reading URLs with parameters such as the user one? Is there something missing?
I've updated Tornado to the lastest version but it didn't work as well.
If you need more information or something is not clear please let me know.
Thanks in advance,
The reason that you aren't seeing the entire string is that Tornado has already parsed it and placed it into a request object. If you want to see the entire path including the query string, then take a look at request.uri.
In your example, if you were to go to http://localhost:9000/hello_world?x=10&y=33 then request.uri would be set to /hello_world?x=10&y=33.
However, as you state, you are better off using get_argument (or get_arguments) to examine the arguments. Here's your example, augmented to show how you can read them.
Try going to http://localhost:9000/distance?x1=0&y1=0&x2=100&y2=100 and see what you get back.
Similarly, try, http://localhost:9000/?user=1822&catid=48&skus=AB1,FS35S,98KSU1. This should now work as you expected.
#!/usr/bin/env python
import math
import tornado.httpserver
import tornado.escape
import tornado.ioloop
import tornado.web
class DistanceService(tornado.web.RequestHandler):
def get(self):
x1 = float(self.get_argument('x1'))
y1 = float(self.get_argument('y1'))
x2 = float(self.get_argument('x2'))
y2 = float(self.get_argument('y2'))
dx = x1 - x2
dy = y1 - y2
magnitude = math.sqrt(dx * dx + dy * dy)
self.write("the distance between the points is %3.2f" % magnitude)
class WService(tornado.web.RequestHandler):
def get(self):
self.write("value of request.uri: %s" % self.request.uri)
self.write("<br>")
self.write("value of request.path: %s" % self.request.path)
self.write("<br>")
self.write("value of request.query: %s" % self.request.query)
application = tornado.web.Application([
(r"/distance", DistanceService),
(r"/", WService),
])
if __name__ == "__main__":
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(9000)
tornado.ioloop.IOLoop.instance().start()

Bottle.py error routing

Bottle.py ships with an import to handle throwing HTTPErrors and route to a function.
Firstly, the documentation claims I can (and so do several examples):
from bottle import error
#error(500)
def custom500(error):
return 'my custom message'
however, when importing this statement error is unresolved but on running the application ignores this and just directs me to the generic error page.
I found a way to get around this by:
from bottle import Bottle
main = Bottle()
#Bottle.error(main, 500)
def custom500(error):
return 'my custom message'
But this code prevents me from embedding my errors all in a separate module to control the nastiness that would ensue if I kept them in my main.py module because the first argument has to be a bottle instance.
So my questions:
Has anyone else experienced this?
why doesn't error seem to resolve in only my case (I installed from pip install bottle)?
Is there a seamless way to import my error routing from a separate python module into the main application?
If you want to embed your errors in another module, you could do something like this:
error.py
def custom500(error):
return 'my custom message'
handler = {
500: custom500,
}
app.py
from bottle import *
import error
app = Bottle()
app.error_handler = error.handler
#app.route('/')
def divzero():
return 1/0
run(app)
This works for me:
from bottle import error, run, route, abort
#error(500)
def custom500(error):
return 'my custom message'
#route("/")
def index():
abort("Boo!")
run()
In some cases I find it's better to subclass Bottle. Here's an example of doing that and adding a custom error handler.
#!/usr/bin/env python3
from bottle import Bottle, response, Route
class MyBottle(Bottle):
def __init__(self, *args, **kwargs):
Bottle.__init__(self, *args, **kwargs)
self.error_handler[404] = self.four04
self.add_route(Route(self, "/helloworld", "GET", self.helloworld))
def helloworld(self):
response.content_type = "text/plain"
yield "Hello, world."
def four04(self, httperror):
response.content_type = "text/plain"
yield "You're 404."
if __name__ == '__main__':
mybottle = MyBottle()
mybottle.run(host='localhost', port=8080, quiet=True, debug=True)

Categories

Resources