2013-11-10 01:46:09 +00:00
|
|
|
#----------------------------------------------------------
|
|
|
|
# ir_http modular http routing
|
|
|
|
#----------------------------------------------------------
|
2015-03-26 10:44:01 +00:00
|
|
|
import datetime
|
|
|
|
import hashlib
|
2013-11-10 01:46:09 +00:00
|
|
|
import logging
|
2015-03-26 10:44:01 +00:00
|
|
|
import mimetypes
|
2013-11-15 12:48:19 +00:00
|
|
|
import re
|
2013-12-03 19:06:39 +00:00
|
|
|
import sys
|
2013-11-10 01:46:09 +00:00
|
|
|
|
2015-03-26 10:44:01 +00:00
|
|
|
import werkzeug
|
2014-07-06 14:44:26 +00:00
|
|
|
import werkzeug.exceptions
|
|
|
|
import werkzeug.routing
|
|
|
|
import werkzeug.urls
|
|
|
|
import werkzeug.utils
|
2013-11-10 01:46:09 +00:00
|
|
|
|
|
|
|
import openerp
|
2014-07-06 14:44:26 +00:00
|
|
|
import openerp.exceptions
|
2014-08-22 15:51:20 +00:00
|
|
|
import openerp.models
|
2013-11-10 01:46:09 +00:00
|
|
|
from openerp import http
|
|
|
|
from openerp.http import request
|
2013-11-15 10:09:01 +00:00
|
|
|
from openerp.osv import osv, orm
|
2013-11-10 01:46:09 +00:00
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
2014-01-23 15:12:07 +00:00
|
|
|
UID_PLACEHOLDER = object()
|
2013-11-10 12:37:07 +00:00
|
|
|
|
|
|
|
class ModelConverter(werkzeug.routing.BaseConverter):
|
|
|
|
|
|
|
|
def __init__(self, url_map, model=False):
|
|
|
|
super(ModelConverter, self).__init__(url_map)
|
|
|
|
self.model = model
|
|
|
|
self.regex = '([0-9]+)'
|
|
|
|
|
|
|
|
def to_python(self, value):
|
2013-11-15 12:48:19 +00:00
|
|
|
m = re.match(self.regex, value)
|
|
|
|
return request.registry[self.model].browse(
|
2014-01-23 15:12:07 +00:00
|
|
|
request.cr, UID_PLACEHOLDER, int(m.group(1)), context=request.context)
|
2013-11-10 12:37:07 +00:00
|
|
|
|
|
|
|
def to_url(self, value):
|
|
|
|
return value.id
|
|
|
|
|
|
|
|
class ModelsConverter(werkzeug.routing.BaseConverter):
|
|
|
|
|
|
|
|
def __init__(self, url_map, model=False):
|
|
|
|
super(ModelsConverter, self).__init__(url_map)
|
|
|
|
self.model = model
|
|
|
|
# TODO add support for slug in the form [A-Za-z0-9-] bla-bla-89 -> id 89
|
|
|
|
self.regex = '([0-9,]+)'
|
|
|
|
|
|
|
|
def to_python(self, value):
|
2014-01-23 15:12:07 +00:00
|
|
|
return request.registry[self.model].browse(request.cr, UID_PLACEHOLDER, [int(i) for i in value.split(',')], context=request.context)
|
2013-11-10 12:37:07 +00:00
|
|
|
|
|
|
|
def to_url(self, value):
|
2013-11-15 10:09:01 +00:00
|
|
|
return ",".join(i.id for i in value)
|
2013-11-10 12:37:07 +00:00
|
|
|
|
2013-11-10 13:54:06 +00:00
|
|
|
class ir_http(osv.AbstractModel):
|
2013-11-10 01:46:09 +00:00
|
|
|
_name = 'ir.http'
|
|
|
|
_description = "HTTP routing"
|
|
|
|
|
2013-11-10 14:12:50 +00:00
|
|
|
def _get_converters(self):
|
|
|
|
return {'model': ModelConverter, 'models': ModelsConverter}
|
|
|
|
|
2014-03-10 13:03:49 +00:00
|
|
|
def _find_handler(self, return_rule=False):
|
|
|
|
return self.routing_map().bind_to_environ(request.httprequest.environ).match(return_rule=return_rule)
|
2013-11-10 01:46:09 +00:00
|
|
|
|
|
|
|
def _auth_method_user(self):
|
|
|
|
request.uid = request.session.uid
|
|
|
|
if not request.uid:
|
2013-11-15 10:09:01 +00:00
|
|
|
raise http.SessionExpiredException("Session expired")
|
2013-11-10 01:46:09 +00:00
|
|
|
|
|
|
|
def _auth_method_none(self):
|
|
|
|
request.uid = None
|
|
|
|
|
2014-01-28 14:55:10 +00:00
|
|
|
def _auth_method_public(self):
|
|
|
|
if not request.session.uid:
|
|
|
|
dummy, request.uid = self.pool['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'base', 'public_user')
|
|
|
|
else:
|
|
|
|
request.uid = request.session.uid
|
|
|
|
|
2013-12-03 19:06:39 +00:00
|
|
|
def _authenticate(self, auth_method='user'):
|
2014-06-18 12:31:16 +00:00
|
|
|
try:
|
|
|
|
if request.session.uid:
|
|
|
|
try:
|
|
|
|
request.session.check_security()
|
|
|
|
# what if error in security.check()
|
|
|
|
# -> res_users.check()
|
|
|
|
# -> res_users.check_credentials()
|
|
|
|
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException):
|
|
|
|
# All other exceptions mean undetermined status (e.g. connection pool full),
|
|
|
|
# let them bubble up
|
2014-10-17 15:09:01 +00:00
|
|
|
request.session.logout(keep_db=True)
|
2014-06-18 12:31:16 +00:00
|
|
|
getattr(self, "_auth_method_%s" % auth_method)()
|
2015-01-19 12:43:57 +00:00
|
|
|
except (openerp.exceptions.AccessDenied, openerp.http.SessionExpiredException, werkzeug.exceptions.HTTPException):
|
2014-06-18 12:31:16 +00:00
|
|
|
raise
|
|
|
|
except Exception:
|
|
|
|
_logger.exception("Exception during request Authentication.")
|
|
|
|
raise openerp.exceptions.AccessDenied()
|
2013-11-10 01:46:09 +00:00
|
|
|
return auth_method
|
|
|
|
|
2015-03-26 10:44:01 +00:00
|
|
|
def _serve_attachment(self):
|
|
|
|
domain = [('type', '=', 'binary'), ('url', '=', request.httprequest.path)]
|
|
|
|
attach = self.pool['ir.attachment'].search_read(
|
|
|
|
request.cr, openerp.SUPERUSER_ID, domain,
|
2015-04-02 14:00:34 +00:00
|
|
|
['__last_update', 'datas', 'datas_fname', 'name'],
|
2015-03-26 10:44:01 +00:00
|
|
|
context=request.context)
|
|
|
|
if attach:
|
|
|
|
wdate = attach[0]['__last_update']
|
2015-04-02 14:00:34 +00:00
|
|
|
datas = attach[0]['datas'] or ''
|
|
|
|
name = attach[0]['name']
|
|
|
|
|
2015-08-17 14:05:15 +00:00
|
|
|
if (not datas and name != request.httprequest.path and
|
|
|
|
name.startswith(('http://', 'https://', '/'))):
|
|
|
|
return werkzeug.utils.redirect(name, 301)
|
2015-04-02 14:00:34 +00:00
|
|
|
|
2015-03-26 10:44:01 +00:00
|
|
|
response = werkzeug.wrappers.Response()
|
|
|
|
server_format = openerp.tools.misc.DEFAULT_SERVER_DATETIME_FORMAT
|
|
|
|
try:
|
|
|
|
response.last_modified = datetime.datetime.strptime(wdate, server_format + '.%f')
|
|
|
|
except ValueError:
|
|
|
|
# just in case we have a timestamp without microseconds
|
|
|
|
response.last_modified = datetime.datetime.strptime(wdate, server_format)
|
|
|
|
|
|
|
|
response.set_etag(hashlib.sha1(datas).hexdigest())
|
|
|
|
response.make_conditional(request.httprequest)
|
|
|
|
|
|
|
|
if response.status_code == 304:
|
|
|
|
return response
|
|
|
|
|
2015-04-02 14:00:34 +00:00
|
|
|
response.mimetype = (mimetypes.guess_type(attach[0]['datas_fname'] or '')[0] or
|
|
|
|
'application/octet-stream')
|
2015-03-26 10:44:01 +00:00
|
|
|
response.data = datas.decode('base64')
|
|
|
|
return response
|
|
|
|
|
2013-12-03 19:06:39 +00:00
|
|
|
def _handle_exception(self, exception):
|
2015-03-26 10:44:01 +00:00
|
|
|
# This is done first as the attachment path may
|
|
|
|
# not match any HTTP controller.
|
2015-05-21 11:46:38 +00:00
|
|
|
if isinstance(exception, werkzeug.exceptions.HTTPException) and exception.code == 404:
|
|
|
|
attach = self._serve_attachment()
|
|
|
|
if attach:
|
|
|
|
return attach
|
2015-03-26 10:44:01 +00:00
|
|
|
|
2014-03-28 14:27:48 +00:00
|
|
|
# If handle_exception returns something different than None, it will be used as a response
|
2014-09-04 14:31:30 +00:00
|
|
|
try:
|
|
|
|
return request._handle_exception(exception)
|
|
|
|
except openerp.exceptions.AccessDenied:
|
|
|
|
return werkzeug.exceptions.Forbidden()
|
2013-11-10 01:46:09 +00:00
|
|
|
|
|
|
|
def _dispatch(self):
|
|
|
|
# locate the controller method
|
|
|
|
try:
|
2014-03-10 13:03:49 +00:00
|
|
|
rule, arguments = self._find_handler(return_rule=True)
|
|
|
|
func = rule.endpoint
|
2013-11-10 01:46:09 +00:00
|
|
|
except werkzeug.exceptions.NotFound, e:
|
2013-12-03 19:06:39 +00:00
|
|
|
return self._handle_exception(e)
|
2013-11-10 01:46:09 +00:00
|
|
|
|
|
|
|
# check authentication level
|
|
|
|
try:
|
2014-01-20 15:36:14 +00:00
|
|
|
auth_method = self._authenticate(func.routing["auth"])
|
2014-09-04 14:31:30 +00:00
|
|
|
except Exception as e:
|
|
|
|
return self._handle_exception(e)
|
2013-11-10 01:46:09 +00:00
|
|
|
|
2014-03-10 13:03:49 +00:00
|
|
|
processing = self._postprocess_args(arguments, rule)
|
2014-01-27 13:13:33 +00:00
|
|
|
if processing:
|
|
|
|
return processing
|
|
|
|
|
2013-11-10 12:37:07 +00:00
|
|
|
|
2013-11-10 01:46:09 +00:00
|
|
|
# set and execute handler
|
|
|
|
try:
|
|
|
|
request.set_handler(func, arguments, auth_method)
|
|
|
|
result = request.dispatch()
|
2013-11-15 10:09:01 +00:00
|
|
|
if isinstance(result, Exception):
|
|
|
|
raise result
|
2013-11-10 01:46:09 +00:00
|
|
|
except Exception, e:
|
2013-12-03 19:06:39 +00:00
|
|
|
return self._handle_exception(e)
|
2013-11-10 14:27:31 +00:00
|
|
|
|
2013-11-10 01:46:09 +00:00
|
|
|
return result
|
|
|
|
|
2014-03-10 13:03:49 +00:00
|
|
|
def _postprocess_args(self, arguments, rule):
|
2014-01-24 10:35:46 +00:00
|
|
|
""" post process arg to set uid on browse records """
|
2014-08-22 15:51:20 +00:00
|
|
|
for name, arg in arguments.items():
|
2014-01-24 10:35:46 +00:00
|
|
|
if isinstance(arg, orm.browse_record) and arg._uid is UID_PLACEHOLDER:
|
2014-08-22 15:51:20 +00:00
|
|
|
arguments[name] = arg.sudo(request.uid)
|
2014-01-24 10:35:46 +00:00
|
|
|
try:
|
2014-08-22 15:51:20 +00:00
|
|
|
arg.exists()
|
|
|
|
except openerp.models.MissingError:
|
2014-01-24 10:35:46 +00:00
|
|
|
return self._handle_exception(werkzeug.exceptions.NotFound())
|
|
|
|
|
2013-11-15 10:09:01 +00:00
|
|
|
def routing_map(self):
|
|
|
|
if not hasattr(self, '_routing_map'):
|
|
|
|
_logger.info("Generating routing map")
|
|
|
|
cr = request.cr
|
|
|
|
m = request.registry.get('ir.module.module')
|
|
|
|
ids = m.search(cr, openerp.SUPERUSER_ID, [('state', '=', 'installed'), ('name', '!=', 'web')], context=request.context)
|
|
|
|
installed = set(x['name'] for x in m.read(cr, 1, ids, ['name'], context=request.context))
|
2014-02-17 00:51:37 +00:00
|
|
|
if openerp.tools.config['test_enable']:
|
|
|
|
installed.add(openerp.modules.module.current_test)
|
2014-01-30 16:26:26 +00:00
|
|
|
mods = [''] + openerp.conf.server_wide_modules + sorted(installed)
|
2013-11-15 10:09:01 +00:00
|
|
|
self._routing_map = http.routing_map(mods, False, converters=self._get_converters())
|
|
|
|
|
|
|
|
return self._routing_map
|
|
|
|
|
2013-12-03 19:06:39 +00:00
|
|
|
def convert_exception_to(to_type, with_message=False):
|
|
|
|
""" Should only be called from an exception handler. Fetches the current
|
|
|
|
exception data from sys.exc_info() and creates a new exception of type
|
|
|
|
``to_type`` with the original traceback.
|
|
|
|
|
|
|
|
If ``with_message`` is ``True``, sets the new exception's message to be
|
|
|
|
the stringification of the original exception. If ``False``, does not
|
|
|
|
set the new exception's message. Otherwise, uses ``with_message`` as the
|
|
|
|
new exception's message.
|
|
|
|
|
|
|
|
:type with_message: str|bool
|
|
|
|
"""
|
|
|
|
etype, original, tb = sys.exc_info()
|
|
|
|
try:
|
|
|
|
if with_message is False:
|
|
|
|
message = None
|
|
|
|
elif with_message is True:
|
|
|
|
message = str(original)
|
|
|
|
else:
|
|
|
|
message = str(with_message)
|
|
|
|
|
|
|
|
raise to_type, message, tb
|
|
|
|
except to_type, e:
|
|
|
|
return e
|
|
|
|
|
2013-11-10 01:46:09 +00:00
|
|
|
# vim:et:
|