Redisgn mapping to be more fexible, use only one dispatcher and begin

password / site association

* auth/authform.py: class to allow auth form
* config.py: new configuration
* dispatchers/default.py -> dispatcher.py: move default dispatcher
* server.py: change to support the new conf file
* templates/associate.html: html template to associate an account
This commit is contained in:
Jérôme Schneider 2011-09-13 19:13:14 +02:00
parent 6d64b0d2f9
commit 7285c43a26
9 changed files with 183 additions and 111 deletions

88
mandaye/auth/authform.py Normal file
View File

@ -0,0 +1,88 @@
import ipdb
"""
Dispatcher for basic auth form authentifications
"""
import Cookie
import httplib
import urllib
from urlparse import parse_qs
from mandaye.http import HTTPResponse
from mandaye.response import _500, _302, _401
from mandaye.response import template_response
class AuthForm(object):
def __init__(self, local_auth, form_action, from_headers,
form_values, username_field, password_field):
""" local_auth: instance of your local authentification system
"""
self.local_auth = local_auth
self.form_action = form_action
self.from_headers = from_headers
self.form_values = form_values
self.username_field = username_field
self.password_field = password_field
def _replay(self, env, username, password):
""" replay the login / password
"""
headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
params = self.form_values
# TODO: find the login / password in the destaut or redirect
params[self.username_field] = username
params[self.password_field] = password
params = urllib.urlencode(params)
conn = httplib.HTTPConnection(env['target'].hostname)
conn.request("POST", self.form_action, params, headers)
response = conn.getresponse()
conn.close()
return response
def associate(self, env, values, request):
""" Render the associate page
"""
return template_response(values['template'], values)
def associate_submit(self, env, values, request, condition=None):
"""
"""
session = env['beaker.session']
if request.msg:
post = parse_qs(request.msg.read())
response = self._replay(env, post['username'][0], post['password'][0])
if eval(condition):
if not session.has_key('pseudo'):
# Find a way to put this in the configuration
return _302("/mandaye/connection")
else:
# TODO associate this login / password in the db
pass
return template_response(values['template'], values)
def check_auth(self, env, values, request=None):
""" values: dict witth username, login
"""
pass
def login(self, env, values, request):
""" Automatic login on a site with a form
"""
login = self.local_auth.get_current_login(env)
if not login:
return _401('Access denied: invalid token')
#if not values.has_key('form_action') \
# or not values.has_key('form_values') \
# or not values.has_key('username_field') \
# or not values.has_key('password_field'):
# return _500(env['PATH_INFO'],
# 'Invalid values for AuthFormDispatcher.login')
# TODO: Test get the username / password here
#response = self._replay(env,
#return _302(location, cookies)
def connection(self, values, response):
pass

View File

@ -1,5 +1,4 @@
"""
Dispatcher for the Vincennes authentifications
""" Vincennes authentifications
"""
import base64
import logging
@ -12,8 +11,8 @@ from mandaye.response import _502, _302
class VincennesAuth(object):
def __init__(self, env):
self.env = env
def __init__(self, url):
self.url = url
def _parse_qs(self, query):
""" Parse query string
@ -26,14 +25,14 @@ class VincennesAuth(object):
res[keyvalue[0]] = keyvalue[1]
return res
def get_current_login(self):
def get_current_login(self, env):
""" Return the current Vincennes pseudo
"""
from mandaye import config
# TODO: test time validity
if not self.env['QUERY_STRING']:
if not env['QUERY_STRING']:
return None
query = self._parse_qs(self.env['QUERY_STRING'])
query = self._parse_qs(env['QUERY_STRING'])
if query.has_key('token'):
# TODO: catch exceptions
try:
@ -42,6 +41,10 @@ class VincennesAuth(object):
cipher = AES.new(config.secret, AES.MODE_CFB)
decode = cipher.decrypt(token)
info = eval(decode[16:])
session = env['beaker.session']
session['pseudo'] = info['pseudo']
session.save()
print info['pseudo']
return info['pseudo']
except Exception, e:
if config.debug:
@ -49,19 +52,11 @@ class VincennesAuth(object):
return None
return None
class VincennesDispatcher(AuthFormDispatcher):
def init(self, env):
""" overload the init method to change the backends
"""
super(VincennesDispatcher, self).init(env)
self.local_auth = VincennesAuth(env)
def connection(self, values, request):
def connection(self, env, values, request):
""" Connection to the compte citoyen
"""
location = values.get('destination')
location += "?next_url=%s&service=%s" % \
(values.get('next_url'), values.get('service_name'))
location = "%s?next_url=%s&service=%s" % \
(self.url, values.get('next_url'), values.get('service_name'))
return _302(location)

View File

@ -19,14 +19,16 @@ template_directory = "mandaye/templates"
db_url = 'sqlite:///test.db'
#example_mapping = {
# r'/': {
#example_mapping = [
# {
# 'path': r'/'
# 'on_response_filters': [{
# 'type': 'data',
# 'filter': my_filter
# }]
# },
# r'/test$': {
# {
# 'path': r'/test$'
# 'method': 'GET',
# 'response': {
# 'dispatcher_method': 'my_test',
@ -35,19 +37,24 @@ db_url = 'sqlite:///test.db'
# }
# }
# },
#}
#]
from dispatchers.default import Dispatcher
hosts = {
'localhost:8088': [
(r'/', Dispatcher('http://perdu.com/')),
{'path': r'/',
'target': 'http://perdu.com',
'mapping': None
},
],
}
session_opts = {
'session.type': 'file',
'session.cookie_expires': True,
'session.data_dir': '/tmp',
'session.expire': 3600,
}
from mandaye.local_config import *

View File

@ -13,18 +13,14 @@ class Dispatcher(object):
It allows you to launch the right filter on the reqest and the response
"""
def __init__(self, target_url, mapping=dict()):
def __init__(self, env, target_url, mapping=dict()):
""" target_url: the full url of your destination
"""
self.target = urlparse(target_url)
self.env = None
self.mapping = mapping
def init(self, env):
self.env = env
self.env['target'] = self.target
self.mapping = mapping
self.filter = MandayeFilter(self.env, self.target)
path = self.env['PATH_INFO']
self.req_mapping = self._parse_mapping(self.mapping)
def _parse_mapping(self, mapping):
@ -38,31 +34,25 @@ class Dispatcher(object):
'response': None,
}
for path in mapping.iterkeys():
if re.match(path, self.env['PATH_INFO']):
cmapping = self.mapping[path]
if not cmapping.has_key('method') \
or cmapping['method'] == self.env['REQUEST_METHOD']:
for conf in mapping:
if conf.has_key('path') and \
re.match(conf['path'], self.env['PATH_INFO']):
if not conf.has_key('method') \
or conf['method'] == self.env['REQUEST_METHOD']:
for hookname in req_mapping:
if cmapping.has_key(hookname):
if conf.has_key(hookname):
if isinstance(req_mapping[hookname], list):
req_mapping[hookname].extend(cmapping[hookname])
req_mapping[hookname].extend(conf[hookname])
else:
req_mapping[hookname] = cmapping[hookname]
req_mapping[hookname] = conf[hookname]
return req_mapping
def _call_dispatcher_hook(self, hook, *args):
if hook and hook.has_key('dispatcher_method'):
try:
method = getattr(self, hook['dispatcher_method'])
except AttributeError:
logging.warning('%s: Invalid dispatcher method for hook %s' % \
(self.env['PATH_INFO'], hookname))
else:
values = hook.get('values')
if not values:
values = dict()
return method(values, *args)
if hook and hook.has_key('method'):
values = hook.get('values')
if not values:
values = dict()
return hook['method'](self.env, values, *args)
return None
def get_target_url(self):
@ -81,10 +71,18 @@ class Dispatcher(object):
def get_response(self, request):
""" Called if you have a response hook for this request
"""
response = self._call_dispatcher_hook(self.req_mapping['response'],
request)
if self.req_mapping['response'].has_key('condition'):
response = self._call_dispatcher_hook(self.req_mapping['response'],
request, self.req_mapping['response']['condition'])
else:
response = self._call_dispatcher_hook(self.req_mapping['response'],
request)
if not response:
return _500(self.env["PATH_INFO"], "The response hook failed")
# loading defaults filters
response.headers = self.filter.response_headers(response.headers)
response.cookies = self.filter.response_cookies(response.cookies)
response.msg = self.filter.response_msg(response.msg)
return response
def mod_request(self, request):
@ -117,7 +115,7 @@ class Dispatcher(object):
response: MandayeResponse object with cookies, headers and HTML
you can modify the cookies and the HTTP headers """
# Loading defaults filters
# loading defaults filters
response.headers = self.filter.response_headers(response.headers)
response.cookies = self.filter.response_cookies(response.cookies)
response.msg = self.filter.response_msg(response.msg)

View File

@ -1,52 +0,0 @@
"""
Dispatcher for basic auth form authentifications
"""
import Cookie
import httplib
import urllib
from mandaye.dispatchers.default import Dispatcher
from mandaye.response import _502, _302, _401
class AuthFormDispatcher(Dispatcher):
def __init__(self, target_url, mapping=None):
super(AuthFormDispatcher, self).__init__(target_url, mapping)
self.local_auth = None
self.dest_auth = None
def login(self, values, request):
""" Automatic login on a site with a form
"""
login = self.local_auth.get_current_login()
if not login:
return _401('Access denied: invalid token')
if not values.has_key('form_action') \
or not values.has_key('form_values') \
or not values.has_key('username_field') \
or not values.has_key('password_field'):
return _500(self.env['PATH_INFO'],
'Invalid values for AuthFormDispatcher.login')
headers = values.get('form_headers')
if not headers:
headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
params = values['form_values']
# TODO: find the login / password in the destaut or redirect
params[values['username_field']] = '754443'
params[values['password_field']] = ''
params = urllib.urlencode(params)
conn = httplib.HTTPConnection(self.target.hostname)
conn.request("POST", values.get('form_action'), params, headers)
res = conn.getresponse()
cookies = Cookie.BaseCookie(res.msg.getheader('set-cookie'))
cookies = self.filter.response_cookies(cookies)
location = res.msg.getheader('location')
if location:
location = location.replace(self.target.hostname, self.env["HTTP_HOST"])
conn.close()
# TODO: Test if he is login or not
return _302(location, cookies)
def connection(self, values, response):
pass

View File

@ -13,7 +13,7 @@ mylookup = TemplateLookup(directories=[template_directory])
def serve_template(templatename, **kwargs):
mytemplate = mylookup.get_template(templatename)
return mytemplate.render(**kwargs)
return mytemplate.render_unicode(**kwargs)
def _get_html_traceback(exception, path):
traceback.print_exc()
@ -22,7 +22,6 @@ def _get_html_traceback(exception, path):
(path, exception, escape(tb_str))
return msg
def _302(location, cookies=None):
headers = HTTPHeader({'Location': [location],
'Content-Type': ['text/html']})
@ -78,3 +77,8 @@ administrator and inform them of the time the error occurred."
return HTTPResponse(502, 'Bad Gateway', headers,
serve_template("response.html", title=title, body=msg))
def template_response(templatename, values):
headers = HTTPHeader({'Content-Type': ['text/html']})
msg = serve_template(templatename, **values)
return HTTPResponse(200, 'OK', headers, msg)

View File

@ -1,5 +1,6 @@
from gevent import monkey
# gevent patching
from gevent import monkey
monkey.patch_all()
import Cookie
@ -10,12 +11,13 @@ import urllib2
import re
import sys
import traceback
import poster.streaminghttp
from beaker.middleware import SessionMiddleware
from cgi import escape
from gevent.pywsgi import WSGIServer
from mandaye.dispatcher import Dispatcher
from mandaye.handlers.default import MandayeRedirectHandler, MandayeErrorHandler
from mandaye.http import HTTPHeader, HTTPRequest, HTTPResponse
from mandaye.response import _404, _502
@ -37,7 +39,6 @@ class MandayeApp(object):
self.env = env
self.dispatcher = self._get_dispatcher(env['HTTP_HOST'], env['PATH_INFO'])
if self.dispatcher:
self.dispatcher.init(env)
return self.on_request(start_response)
else:
return self.on_response(start_response, _404(env['PATH_INFO']))
@ -47,8 +48,9 @@ class MandayeApp(object):
"""
if config.hosts.has_key(local_host):
for mapper in config.hosts[local_host]:
if re.match(mapper[0], path_info):
return mapper[1]
if re.match(mapper.get('path'), path_info):
return Dispatcher(self.env, mapper.get('target'),
mapper.get('mapping'))
return None
def _get_request(self):

View File

@ -0,0 +1,30 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="fr-fr" />
<title>1er connection</title>
</head>
<h3>1er connection</h3>
% if error_msg:
<p>${error_msg}</p>
% elif description:
<p>${description}</p>
% endif
<form action="${action}" method="post" accept-charset="utf-8">
<div>
<label for="username">${username_label}</label>
<input type="text" name="username" value="" id="username" />
</div>
<div>
<label for="password">${password_label}</label>
<input type="password" name="password" value="" id="password" />
</div>
<p><input type="submit" value="Enregistrer"></p>
</form>
</html>