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:
parent
6d64b0d2f9
commit
7285c43a26
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
@ -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 *
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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>
|
Reference in New Issue