From cef6ffead0bc46dc77a87d483297e6e805cdb622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 18 Jan 2019 11:01:22 +0100 Subject: [PATCH] Allow to redirect bus request issue7943 review48691003 --- CHANGELOG | 3 +++ doc/topics/configuration.rst | 18 ++++++++++++++++++ trytond/bus.py | 15 +++++++++++++++ trytond/config.py | 1 + trytond/protocols/dispatcher.py | 6 ++++++ trytond/wsgi.py | 29 ++++++++++++++++++++++++++++- 6 files changed, 71 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index e0039e3b5b..8f6dea2171 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +* Add cache timeout for web +* Allow the bus requests to be redirected to another host +* Add support for CORS * Add extras modules to ModuleTestCase * Add timeout to Report.convert * Allow records from XML with noupdate to be deleted diff --git a/doc/topics/configuration.rst b/doc/topics/configuration.rst index 894651be85..61f0665f68 100644 --- a/doc/topics/configuration.rst +++ b/doc/topics/configuration.rst @@ -64,6 +64,19 @@ The number of proxy servers in front of `trytond`. Default: 0 +cache_timeout +~~~~~~~~~~~~~ + +The cache timeout in seconds. + +Default: 12h + +cors +~~~~ + +The list (one per line) of origins allowed for `Cross-Origin Resource sharing +`_. + database -------- @@ -382,6 +395,11 @@ A boolean value to allow clients to subscribe to bus channels. Default: `False` +url_host +~~~~~~~~ + +If set redirects bus requests to the host URL. + long_polling_timeout ~~~~~~~~~~~~~~~~~~~~ diff --git a/trytond/bus.py b/trytond/bus.py index 8bde39f71f..6de02b12f0 100644 --- a/trytond/bus.py +++ b/trytond/bus.py @@ -8,7 +8,13 @@ import select import threading import time import uuid +try: + from http import HTTPStatus +except ImportError: + from http import client as HTTPStatus +from urllib.parse import urljoin +from werkzeug.utils import redirect from werkzeug.wrappers import Response from werkzeug.exceptions import NotImplemented, BadRequest @@ -27,6 +33,8 @@ _cache_timeout = config.getint('bus', 'cache_timeout') _select_timeout = config.getint('bus', 'select_timeout') _long_polling_timeout = config.getint('bus', 'long_polling_timeout') _allow_subscribe = config.getboolean('bus', 'allow_subscribe') +_url_host = config.get('bus', 'url_host') +_web_cache_timeout = config.getint('web', 'cache_timeout') class _MessageQueue: @@ -210,6 +218,13 @@ else: def subscribe(request, database_name): if not _allow_subscribe: raise NotImplemented + if _url_host and _url_host != request.host_url: + response = redirect( + urljoin(_url_host, request.path), HTTPStatus.PERMANENT_REDIRECT) + # Allow to change the redirection after some time + response.headers['Cache-Control'] = ( + 'private, max-age=%s' % _web_cache_timeout) + return response user = request.authorization.get('userid') channels = request.parsed_data.get('channels', []) if user is None: diff --git a/trytond/config.py b/trytond/config.py index 4ac9b204a6..f15e1baf82 100644 --- a/trytond/config.py +++ b/trytond/config.py @@ -45,6 +45,7 @@ class TrytonConfigParser(configparser.RawConfigParser): self.set('web', 'listen', 'localhost:8000') self.set('web', 'root', os.path.join(os.path.expanduser('~'), 'www')) self.set('web', 'num_proxies', 0) + self.set('web', 'cache_timeout', 60 * 60 * 12) self.add_section('database') self.set('database', 'uri', os.environ.get('TRYTOND_DATABASE_URI', 'sqlite://')) diff --git a/trytond/protocols/dispatcher.py b/trytond/protocols/dispatcher.py index 73a51f3ad4..be0f8e8ee4 100644 --- a/trytond/protocols/dispatcher.py +++ b/trytond/protocols/dispatcher.py @@ -10,6 +10,7 @@ except ImportError: from http import client as HTTPStatus from werkzeug.exceptions import abort +from werkzeug.wrappers import Response from sql import Table from trytond import security @@ -87,6 +88,11 @@ def root(request, *args): return methods[request.rpc_method](request, *request.rpc_params) +@app.route('/', methods=['OPTIONS']) +def options(request, path): + return Response(status=HTTPStatus.NO_CONTENT) + + def db_exist(request, database_name): Database = backend.get('Database') try: diff --git a/trytond/wsgi.py b/trytond/wsgi.py index b0a32213af..8e40e6518b 100644 --- a/trytond/wsgi.py +++ b/trytond/wsgi.py @@ -5,6 +5,10 @@ import logging import os import sys import traceback +try: + from http import HTTPStatus +except ImportError: + from http import client as HTTPStatus from werkzeug.wrappers import Response from werkzeug.routing import Map, Rule @@ -92,6 +96,14 @@ class TrytondWSGI(object): break else: request = Request(environ) + + origin = request.headers.get('Origin') + if origin: + cors = filter( + None, config.get('web', 'cors', default='').splitlines()) + if origin not in cors: + abort(HTTPStatus.FORBIDDEN) + data = self.dispatch_request(request) if not isinstance(data, (Response, HTTPException)): for cls in self.protocols: @@ -114,6 +126,19 @@ class TrytondWSGI(object): response = Response(data) else: response = data + + if origin: + response.headers['Access-Control-Allow-Origin'] = origin + response.headers['Vary'] = 'Origin' + method = request.headers.get('Access-Control-Request-Method') + if method: + response.headers['Access-Control-Allow-Methods'] = method + headers = request.headers.get('Access-Control-Request-Headers') + if headers: + response.headers['Access-Control-Allow-Headers'] = headers + response.headers['Access-Control-Max-Age'] = config.getint( + 'web', 'cache_timeout') + # TODO custom process response return response(environ, start_response) @@ -147,7 +172,9 @@ if config.get('web', 'root'): static_files = { '/': config.get('web', 'root'), } - app.wsgi_app = SharedDataMiddlewareIndex(app.wsgi_app, static_files) + app.wsgi_app = SharedDataMiddlewareIndex( + app.wsgi_app, static_files, + cache_timeout=config.getint('web', 'cache_timeout')) num_proxies = config.getint('web', 'num_proxies') if num_proxies: app.wsgi_app = ProxyFix(app.wsgi_app, num_proxies=num_proxies)