Werkzeug 0.15 modified ProxyFix such that by default it only forwards
the REMOTE_ADDR when enabled, whereas before 0.15 it would also
forward scheme and host. This breaks proxied odoo as the base url
becomes incorrect (cf #34412).
Use properly configured ProxyFix when running with werkzeug 0.15, old
configuration otherwise.
Backport of 4057227def which was merged
in master, because many people apparently run 0.15 now.
Closes #35085
closes odoo/odoo#36212
closes odoo/odoo#37708
Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
143 lines
5.8 KiB
Python
143 lines
5.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
"""
|
|
|
|
WSGI stack, common code.
|
|
|
|
"""
|
|
|
|
import logging
|
|
import sys
|
|
import threading
|
|
import traceback
|
|
|
|
|
|
try:
|
|
from xmlrpc import client as xmlrpclib
|
|
except ImportError:
|
|
# pylint: disable=bad-python3-import
|
|
import xmlrpclib
|
|
|
|
import werkzeug.exceptions
|
|
import werkzeug.wrappers
|
|
import werkzeug.serving
|
|
import werkzeug.contrib.fixers
|
|
|
|
import odoo
|
|
from odoo.tools import config
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
# XML-RPC fault codes. Some care must be taken when changing these: the
|
|
# constants are also defined client-side and must remain in sync.
|
|
# User code must use the exceptions defined in ``odoo.exceptions`` (not
|
|
# create directly ``xmlrpclib.Fault`` objects).
|
|
RPC_FAULT_CODE_CLIENT_ERROR = 1 # indistinguishable from app. error.
|
|
RPC_FAULT_CODE_APPLICATION_ERROR = 1
|
|
RPC_FAULT_CODE_WARNING = 2
|
|
RPC_FAULT_CODE_ACCESS_DENIED = 3
|
|
RPC_FAULT_CODE_ACCESS_ERROR = 4
|
|
|
|
def xmlrpc_handle_exception_int(e):
|
|
if isinstance(e, odoo.exceptions.UserError):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, odoo.tools.ustr(e.name))
|
|
elif isinstance(e, odoo.exceptions.RedirectWarning):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
|
elif isinstance(e, odoo.exceptions.MissingError):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
|
|
elif isinstance (e, odoo.exceptions.AccessError):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_ERROR, str(e))
|
|
elif isinstance(e, odoo.exceptions.AccessDenied):
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_DENIED, str(e))
|
|
elif isinstance(e, odoo.exceptions.DeferredException):
|
|
info = e.traceback
|
|
# Which one is the best ?
|
|
formatted_info = "".join(traceback.format_exception(*info))
|
|
#formatted_info = odoo.tools.exception_to_unicode(e) + '\n' + info
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
|
|
else:
|
|
info = sys.exc_info()
|
|
# Which one is the best ?
|
|
formatted_info = "".join(traceback.format_exception(*info))
|
|
#formatted_info = odoo.tools.exception_to_unicode(e) + '\n' + info
|
|
fault = xmlrpclib.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
|
|
|
|
return xmlrpclib.dumps(fault, allow_none=None)
|
|
|
|
def xmlrpc_handle_exception_string(e):
|
|
if isinstance(e, odoo.exceptions.UserError):
|
|
fault = xmlrpclib.Fault('warning -- %s\n\n%s' % (e.name, e.value), '')
|
|
elif isinstance(e, odoo.exceptions.RedirectWarning):
|
|
fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '')
|
|
elif isinstance(e, odoo.exceptions.MissingError):
|
|
fault = xmlrpclib.Fault('warning -- MissingError\n\n' + str(e), '')
|
|
elif isinstance(e, odoo.exceptions.AccessError):
|
|
fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '')
|
|
elif isinstance(e, odoo.exceptions.AccessDenied):
|
|
fault = xmlrpclib.Fault('AccessDenied', str(e))
|
|
elif isinstance(e, odoo.exceptions.DeferredException):
|
|
info = e.traceback
|
|
formatted_info = "".join(traceback.format_exception(*info))
|
|
fault = xmlrpclib.Fault(odoo.tools.ustr(e), formatted_info)
|
|
#InternalError
|
|
else:
|
|
info = sys.exc_info()
|
|
formatted_info = "".join(traceback.format_exception(*info))
|
|
fault = xmlrpclib.Fault(odoo.tools.exception_to_unicode(e), formatted_info)
|
|
|
|
return xmlrpclib.dumps(fault, allow_none=None, encoding=None)
|
|
|
|
def _patch_xmlrpc_marshaller():
|
|
# By default, in xmlrpc, bytes are converted to xmlrpclib.Binary object.
|
|
# Historically, odoo is sending binary as base64 string.
|
|
# In python 3, base64.b64{de,en}code() methods now works on bytes.
|
|
# Convert them to str to have a consistent behavior between python 2 and python 3.
|
|
# TODO? Create a `/xmlrpc/3` route prefix that respect the standard and uses xmlrpclib.Binary.
|
|
def dump_bytes(marshaller, value, write):
|
|
marshaller.dump_unicode(odoo.tools.ustr(value), write)
|
|
|
|
xmlrpclib.Marshaller.dispatch[bytes] = dump_bytes
|
|
|
|
def application_unproxied(environ, start_response):
|
|
""" WSGI entry point."""
|
|
# cleanup db/uid trackers - they're set at HTTP dispatch in
|
|
# web.session.OpenERPSession.send() and at RPC dispatch in
|
|
# odoo.service.web_services.objects_proxy.dispatch().
|
|
# /!\ The cleanup cannot be done at the end of this `application`
|
|
# method because werkzeug still produces relevant logging afterwards
|
|
if hasattr(threading.current_thread(), 'uid'):
|
|
del threading.current_thread().uid
|
|
if hasattr(threading.current_thread(), 'dbname'):
|
|
del threading.current_thread().dbname
|
|
if hasattr(threading.current_thread(), 'url'):
|
|
del threading.current_thread().url
|
|
|
|
with odoo.api.Environment.manage():
|
|
result = odoo.http.root(environ, start_response)
|
|
if result is not None:
|
|
return result
|
|
|
|
# We never returned from the loop.
|
|
return werkzeug.exceptions.NotFound("No handler found.\n")(environ, start_response)
|
|
|
|
try:
|
|
# werkzeug >= 0.15
|
|
from werkzeug.middleware.proxy_fix import ProxyFix as ProxyFix_
|
|
# 0.15 also supports port and prefix, but 0.14 only forwarded for, proto
|
|
# and host so replicate that
|
|
ProxyFix = lambda app: ProxyFix_(app, x_for=1, x_proto=1, x_host=1)
|
|
except ImportError:
|
|
# werkzeug < 0.15
|
|
from werkzeug.contrib.fixers import ProxyFix
|
|
|
|
def application(environ, start_response):
|
|
# FIXME: is checking for the presence of HTTP_X_FORWARDED_HOST really useful?
|
|
# we're ignoring the user configuration, and that means we won't
|
|
# support the standardised Forwarded header once werkzeug supports
|
|
# it
|
|
if config['proxy_mode'] and 'HTTP_X_FORWARDED_HOST' in environ:
|
|
return ProxyFix(application_unproxied)(environ, start_response)
|
|
else:
|
|
return application_unproxied(environ, start_response)
|