New dispatcher for form authentification, improve mapping support,
add templates support and big cleaning * TODO: new file * mandaye/config.py: change next_url for biblio mapping and add local configuration support * mandaye/dispatchers/auth.py: rename authentification dispatcher * mandaye/dispatchers/default.py: improve the default dispatcher * mandaye/dispatchers/vincennes.py: login through the compte citoyen * mandaye/filters/vincennes.py: add a login biblio html filter * mandaye/http.py: fix HTTPResponse * mandaye/response.py: improve the responses * mandaye/server.py: cleaning * mandaye/templates/response.html: html template for the responses * setup.py: add breaker, sqlalchemy and mako
This commit is contained in:
parent
2b114abe74
commit
7afc5c247d
|
@ -0,0 +1,8 @@
|
|||
|
||||
* Improve README.rst
|
||||
* Move the default filters into the mapping
|
||||
* Add a debug mode
|
||||
* Mapping should match the url
|
||||
* Manage a folder with the configurations
|
||||
* Finish authentification management
|
||||
|
|
@ -1,20 +1,23 @@
|
|||
|
||||
from dispatchers.default import Dispatcher
|
||||
from dispatchers.vincennes import AuthFormDispatcher
|
||||
#from filters.vincennes import biblio_html_login_page
|
||||
# Mandaye configuration
|
||||
|
||||
host = "127.0.0.1"
|
||||
port = 8088
|
||||
|
||||
# Needed if ssl is activate
|
||||
ssl = False
|
||||
keyfile = "/home/jschneider/wildcard.entrouvert.org.key"
|
||||
certfile = "/home/jschneider/wildcard.entrouvert.org.crt"
|
||||
keyfile = ""
|
||||
certfile = ""
|
||||
|
||||
# Template directory
|
||||
template_directory = "mandaye/templates"
|
||||
|
||||
# Database configuration
|
||||
# Follow the rfc 1738 http://rfc.net/rfc1738.html
|
||||
db_url = 'sqlite:///test.db'
|
||||
|
||||
from filters.vincennes import biblio_html_login_page
|
||||
from filters.default import MandayeFilter
|
||||
|
||||
biblio_mapping = {
|
||||
'/mandaye/login': {
|
||||
'method': 'GET',
|
||||
|
@ -38,19 +41,23 @@ biblio_mapping = {
|
|||
'dispatcher_method': 'connection',
|
||||
'values': {
|
||||
'destination': 'https://test.vincennes.fr/comptecitoyen/auth',
|
||||
'next_url': 'http://biblio.local:8088/'
|
||||
'next_url': 'http://biblio.local:8088/mandaye/login',
|
||||
'service_name': 'biblio'
|
||||
}
|
||||
}
|
||||
},
|
||||
#'/sezhame/page/connexion-abonne': {
|
||||
# 'method': 'GET',
|
||||
# 'on_response_filter': {
|
||||
# 'type': 'html',
|
||||
# 'filter': biblio_html_login_page
|
||||
# }
|
||||
# },
|
||||
'/sezhame/page/connexion-abonne': {
|
||||
'method': 'GET',
|
||||
'on_response_filters': [{
|
||||
'type': 'data',
|
||||
'filter': biblio_html_login_page
|
||||
},]
|
||||
},
|
||||
}
|
||||
|
||||
from dispatchers.default import Dispatcher
|
||||
from dispatchers.vincennes import VincennesDispatcher
|
||||
|
||||
hosts = {
|
||||
'sfd.local:8088': [
|
||||
(r'/', Dispatcher('http://www.sfdiabete.org')),
|
||||
|
@ -59,7 +66,7 @@ hosts = {
|
|||
(r'/', Dispatcher('http://linuxfr.org')),
|
||||
],
|
||||
'biblio.local:8088': [
|
||||
(r'/', AuthFormDispatcher('http://biblio.vincennes.fr', biblio_mapping)),
|
||||
(r'/', VincennesDispatcher('http://biblio.vincennes.fr', biblio_mapping)),
|
||||
],
|
||||
'localhost:8088': [
|
||||
(r'/', Dispatcher('http://localhost')),
|
||||
|
@ -69,6 +76,7 @@ hosts = {
|
|||
session_opts = {
|
||||
'session.type': 'file',
|
||||
'session.cookie_expires': True,
|
||||
}
|
||||
}
|
||||
|
||||
from mandaye.local_config import *
|
||||
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
"""
|
||||
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('Invalid login')
|
||||
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'):
|
||||
logging.warning('Invalid values for AuthFormDispatcher.login')
|
||||
return _502
|
||||
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']] = 'lecteur'
|
||||
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
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
import logging
|
||||
|
||||
from urlparse import urlparse
|
||||
|
||||
from mandaye.filters.default import MandayeFilter
|
||||
from mandaye.response import _502
|
||||
|
||||
# TODO: add an external url mapping
|
||||
|
||||
|
@ -39,13 +39,14 @@ class Dispatcher(object):
|
|||
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)
|
||||
except AttributeError:
|
||||
logging.warning('%s: Invalid dispatcher method for hook %s' % \
|
||||
(self.env['PATH_INFO'], hookname))
|
||||
return None
|
||||
|
||||
def get_target_url(self):
|
||||
|
@ -62,17 +63,32 @@ class Dispatcher(object):
|
|||
return url
|
||||
|
||||
def get_response(self, request):
|
||||
response = self._call_dispatcher_hook('response')
|
||||
# TODO: test if response is not empty
|
||||
""" Called if you have a response hook for this request
|
||||
"""
|
||||
response = self._call_dispatcher_hook('response', request)
|
||||
if not response:
|
||||
return _500("The response dispatcher hook failed for URL %s" %\
|
||||
self.env["PATH_INFO"])
|
||||
return response
|
||||
|
||||
def mod_request(self, request):
|
||||
""" Modify the request
|
||||
request: MandayeRequest object with cookies and headers
|
||||
Return the request object """
|
||||
# Loading defaults filters
|
||||
request.headers = self.filter.request_headers(request.headers)
|
||||
request.cookies = self.filter.request_cookies(request.cookies)
|
||||
request.msg = self.filter.request_msg(request.msg)
|
||||
# Loading specific filters
|
||||
if self.req_mapping and self.req_mapping.has_key('on_request_filters'):
|
||||
for filter in self.req_mapping['on_request_filters']:
|
||||
if filter['type'] == "data":
|
||||
request.msg = filter['filter'](request.msg, self.env)
|
||||
elif filter['type'] == "cookies":
|
||||
request.cookies = filter['filter'](request.cookies, self.env)
|
||||
elif filter['type'] == "headers":
|
||||
request.headers = filter['filter'](request.headers, self.env)
|
||||
# Calling hook function
|
||||
new_request = self._call_dispatcher_hook('on_request_hook', request)
|
||||
if new_request:
|
||||
return new_request
|
||||
|
@ -83,9 +99,19 @@ class Dispatcher(object):
|
|||
request: the Mandaye request
|
||||
response: MandayeResponse object with cookies, headers and HTML
|
||||
you can modify the cookies and the HTTP headers """
|
||||
# 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)
|
||||
# Loading specific filters
|
||||
if self.req_mapping and self.req_mapping.has_key('on_response_filters'):
|
||||
for filter in self.req_mapping['on_response_filters']:
|
||||
if filter['type'] == "data":
|
||||
response.msg = filter['filter'](response.msg, self.env)
|
||||
elif filter['type'] == "cookies":
|
||||
response.cookies = filter['filter'](response.cookies, self.env)
|
||||
elif filter['type'] == "headers":
|
||||
response.headers = filter['filter'](response.headers, self.env)
|
||||
new_response = self._call_dispatcher_hook('on_response_hook', request, response)
|
||||
if new_response:
|
||||
return new_response
|
||||
|
|
|
@ -1,65 +1,60 @@
|
|||
"""
|
||||
Dispatcher for basic auth form authentification
|
||||
Dispatcher for the Vincennes authentifications
|
||||
"""
|
||||
import base64
|
||||
|
||||
from mandaye.dispatchers.default import Dispatcher
|
||||
from Crypto.Cipher import AES
|
||||
|
||||
from mandaye.dispatchers.auth import AuthFormDispatcher
|
||||
from mandaye.response import _502, _302
|
||||
|
||||
class BiblioAuthForm(object):
|
||||
class VincennesAuth(object):
|
||||
|
||||
def __init__(self, target):
|
||||
self.target = target
|
||||
def __init__(self, env):
|
||||
self.env = env
|
||||
|
||||
def is_auth(self, response):
|
||||
if "/sezhame/logout" in response.msg:
|
||||
return True
|
||||
return False
|
||||
def _parse_qs(self, query):
|
||||
""" Parse query string
|
||||
Return a dict
|
||||
"""
|
||||
res = {}
|
||||
values = query.split('&')
|
||||
for value in values:
|
||||
keyvalue = value.split('=', 1)
|
||||
res[keyvalue[0]] = keyvalue[1]
|
||||
return res
|
||||
|
||||
def auth(self, login, request):
|
||||
import urllib, httplib
|
||||
import cookielib
|
||||
values = { 'user': "754443",
|
||||
'password': "XXXXX",
|
||||
'op': "Se connecter",
|
||||
'form_id': 'dk_opac15_login_form' }
|
||||
action = "/sezhame/page/connexion-abonne"
|
||||
#url = 'http://biblio.vincennes.fr/sezhame/page/connexion-abonne'
|
||||
params = urllib.urlencode(values)
|
||||
params = 'user=754443&password=lecteur&op=Se+connecter&form_id=dk_opac15_login_form'
|
||||
headers = { 'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
'Cookie': request.cookies.output(header="", sep=",")
|
||||
}
|
||||
conn = httplib.HTTPConnection(self.target.hostname)
|
||||
conn.request("POST", action, params, headers)
|
||||
res = conn.getresponse()
|
||||
return res.msg.getheaders('set-cookie')
|
||||
def get_current_login(self):
|
||||
""" Return the current Vincennes pseudo
|
||||
"""
|
||||
from mandaye import config
|
||||
# TODO: test time validity
|
||||
if not self.env['QUERY_STRING']:
|
||||
return None
|
||||
query = self._parse_qs(self.env['QUERY_STRING'])
|
||||
if query.has_key('token'):
|
||||
# TODO: catch exceptions
|
||||
token = query['token']
|
||||
token = base64.b64decode(token)
|
||||
cipher = AES.new(config.secret, AES.MODE_CFB)
|
||||
decode = cipher.decrypt(token)
|
||||
info = eval(decode[16:])
|
||||
return info['pseudo']
|
||||
return None
|
||||
|
||||
class AuthFormDispatcher(Dispatcher):
|
||||
class VincennesDispatcher(AuthFormDispatcher):
|
||||
|
||||
def __init__(self, target_url, mapping=None):
|
||||
super(AuthFormDispatcher, self).__init__(target_url, mapping)
|
||||
#self.local_auth = LocalAuthForm(self.target)
|
||||
#self.dest_auth = DestAuthForm(self.target)
|
||||
|
||||
def login(self, values):
|
||||
from mandaye.response import _502
|
||||
return _502
|
||||
#id = self.local_auth.get_current_user():
|
||||
#action = "/sezhame/page/connexion-abonne"
|
||||
#url = 'http://biblio.vincennes.fr/sezhame/page/connexion-abonne'
|
||||
#params = urllib.urlencode(values)
|
||||
#params = 'user=754443&password=lecteur&op=Se+connecter&form_id=dk_opac15_login_form'
|
||||
#headers = { 'Content-Type': 'application/x-www-form-urlencoded',
|
||||
# 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
||||
# 'Cookie': request.cookies.output(header="", sep=",")
|
||||
# }
|
||||
#conn = httplib.HTTPConnection(self.target.hostname)
|
||||
#conn.request("POST", action, params, headers)
|
||||
#res = conn.getresponse()
|
||||
#conn.close()
|
||||
|
||||
def connection(self, request, response):
|
||||
pass
|
||||
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):
|
||||
""" Connection to the compte citoyen
|
||||
"""
|
||||
location = values.get('destination')
|
||||
location += "?next_url=%s&service=%s" % \
|
||||
(values.get('next_url'), values.get('service_name'))
|
||||
return _302(location)
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import re
|
||||
|
||||
def biblio_html_login_page(data, env):
|
||||
""" data: response message body
|
||||
env: Mandaye environment
|
||||
"""
|
||||
data = re.sub("</center></div>",
|
||||
"<a href=\"/mandaye/connection\">Connection via mon compte citoyen</center></div>",
|
||||
data)
|
||||
return data
|
||||
|
|
@ -76,11 +76,13 @@ class HTTPResponse(object):
|
|||
"""
|
||||
self.code = code
|
||||
self.status = status
|
||||
self.headers = headers
|
||||
self.msg = msg
|
||||
self.cookies = cookies
|
||||
if not headers:
|
||||
self.headers = HTTPHeader()
|
||||
if not cookies:
|
||||
self.cookies = Cookie.BaseCookie()
|
||||
self.msg = msg
|
||||
|
||||
def load_from_urllib(self, response):
|
||||
""" response: urrlib.addinfourl
|
||||
|
|
|
@ -1,5 +1,52 @@
|
|||
|
||||
from mandaye.http import HTTPResponse
|
||||
from mako.lookup import TemplateLookup
|
||||
from mako.template import Template
|
||||
|
||||
from mandaye.config import template_directory
|
||||
from mandaye.http import HTTPResponse, HTTPHeader
|
||||
|
||||
mylookup = TemplateLookup(directories=[template_directory])
|
||||
|
||||
def serve_template(templatename, **kwargs):
|
||||
mytemplate = mylookup.get_template(templatename)
|
||||
return mytemplate.render(**kwargs)
|
||||
|
||||
|
||||
def _302(location, cookies=None):
|
||||
headers = HTTPHeader({'Location': [location],
|
||||
'Content-Type': ['text/html']})
|
||||
msg = "Reload the page to get source for: %s" % location
|
||||
return HTTPResponse(302, 'Found', headers, msg, cookies)
|
||||
|
||||
def _401(msg):
|
||||
title='Unauthorized'
|
||||
headers = HTTPHeader({'Content-Type': ['text/html']})
|
||||
return HTTPResponse(401, 'Unauthorized', headers,
|
||||
serve_template("response.html", title=title, body=msg))
|
||||
|
||||
def _404(url):
|
||||
title = "Not Found"
|
||||
body = "The requested URL %s was not found on this server." % url
|
||||
headers = HTTPHeader({'Content-Type': ['text/html']})
|
||||
return HTTPResponse(404, 'Not found', headers,
|
||||
serve_template("response.html", title=title, body=body))
|
||||
|
||||
def _500(msg=None):
|
||||
if not msg:
|
||||
msg = "The server encountered an internal error or misconfiguration \
|
||||
and was unable to completeyour request.Please contact the server \
|
||||
administrator and inform them of the time the error occurred, and \
|
||||
anything you might have done that may have caused the error. \
|
||||
More information about this error may be availablein the server error log."
|
||||
title = 'Internal Server Error'
|
||||
headers = HTTPHeader({'Content-Type': ['text/html']})
|
||||
return HTTPResponse(500, 'Internal Server Error', headers,
|
||||
serve_template("response.html", title=title, body=msg))
|
||||
|
||||
def _502(msg):
|
||||
title = 'Bad Gateway'
|
||||
headers = HTTPHeader({'Content-Type': ['text/html']})
|
||||
return HTTPResponse(502, 'Bad Gateway', headers,
|
||||
serve_template("response.html", title=title, body=msg))
|
||||
|
||||
|
||||
_404 = HTTPResponse(404, 'Not found', {}, '')
|
||||
_502 = HTTPResponse(502, 'Bad Gateway', {}, '')
|
||||
|
|
|
@ -103,10 +103,9 @@ class MandayeApp(object):
|
|||
traceback.print_exc()
|
||||
tb = traceback.format_exc()
|
||||
error_str = escape(str(ex) or ex.__class__.__name__ or 'Error')
|
||||
_502.content = '<h1>%s</h1><h2>%s</h2><pre>%s</pre>' % \
|
||||
(error_str, escape(path), escape(tb))
|
||||
_502.headers = HTTPHeader({'Content-Type': ['text/html']})
|
||||
return self.on_response(start_response, _502)
|
||||
response = _502("<h2>%s %s</h2><pre>%s</pre>" % \
|
||||
(escape(path), error_str, escape(tb)))
|
||||
return self.on_response(start_response, response)
|
||||
else:
|
||||
mresponse = HTTPResponse()
|
||||
mresponse.load_from_urllib(response)
|
||||
|
@ -129,7 +128,6 @@ def serve():
|
|||
keyfile=config.keyfile, certfile=config.certfile)
|
||||
else:
|
||||
s = WSGIServer((config.host, config.port), wsgi_app)
|
||||
s.serve_forever()
|
||||
try:
|
||||
s.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>${title}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>${title}</h1>
|
||||
<p>${body}</p>
|
||||
</body>
|
||||
</html>
|
5
setup.py
5
setup.py
|
@ -17,11 +17,14 @@ setup(name="mandaye",
|
|||
author_email="info@entrouvert.org",
|
||||
maintainer="Jerome Schneider",
|
||||
maintainer_email="jschneider@entrouvert.com",
|
||||
scripts=['mandaye.py'],
|
||||
scripts=['mandayectl'],
|
||||
packages=find_pacakges(),
|
||||
package_data={},
|
||||
install_requires=[
|
||||
'beaker>=1.5',
|
||||
'gevent>=0.13',
|
||||
'mako>=0.3',
|
||||
'poster>=0.8',
|
||||
'sqlachemy>=0.6'
|
||||
],
|
||||
)
|
||||
|
|
Reference in New Issue