diff --git a/modules/authentication_saml/routes.py b/modules/authentication_saml/routes.py index 658fcdeeb8..bb84014dd7 100644 --- a/modules/authentication_saml/routes.py +++ b/modules/authentication_saml/routes.py @@ -156,6 +156,9 @@ def acs(request, pool, identity): with Transaction().set_user(user_id): session = Session.new() + allow_subscribe = config.getboolean( + 'bus', 'allow_subscribe', default=False) + bus_url_host = config.get('bus', 'url_host', default=request.host_url) redirect_url = request.form.get('RelayState') if not redirect_url: redirect_url = http_host() @@ -165,6 +168,7 @@ def acs(request, pool, identity): query.append(('login', login)) query.append(('user_id', user_id)) query.append(('session', session)) + query.append(('bus_url_host', bus_url_host if allow_subscribe else '')) parts = list(parts) parts[3] = urllib.parse.urlencode(query) return redirect(urllib.parse.urlunsplit(parts)) diff --git a/sao/src/bus.js b/sao/src/bus.js index 6ef8f2f980..6aae5ef78e 100644 --- a/sao/src/bus.js +++ b/sao/src/bus.js @@ -16,12 +16,13 @@ wait = wait || 1; last_message = last_message || Sao.Bus.last_message; var session = Sao.Session.current_session; - if (!session) { + if (!session || !session.bus_url_host) { return; } Sao.Bus.listening = true; let channels = Object.keys(Sao.Bus.channel_actions); + let url = new URL(`${session.database}/bus`, session.bus_url_host); Sao.Bus.last_message = last_message; Sao.Bus.request = jQuery.ajax({ headers: { @@ -33,7 +34,7 @@ channels: channels, }), dataType: 'json', - url: '/' + session.database + '/bus', + url: url, type: 'POST', timeout: Sao.config.bus_timeout, }); diff --git a/sao/src/sao.js b/sao/src/sao.js index 3b6c2cfee9..c5c2390e69 100644 --- a/sao/src/sao.js +++ b/sao/src/sao.js @@ -1135,6 +1135,7 @@ var Sao = { login: url.searchParams.get('login'), user_id: parseInt(url.searchParams.get('user_id'), 10), session: url.searchParams.get('session'), + bus_url_host: url.searchParams.get('bus_url_host'), }; if (url.searchParams.has('renew')) { var renew_id = parseInt(url.searchParams.get('renew'), 10); diff --git a/sao/src/session.js b/sao/src/session.js index 5f5d49ff71..2d1519d16b 100644 --- a/sao/src/session.js +++ b/sao/src/session.js @@ -13,6 +13,7 @@ this.login_service = null; this.user_id = null; this.session = null; + this.bus_url_host = null; this.cache = new Cache(); this.prm = jQuery.when(); // renew promise this.database = database; @@ -47,12 +48,14 @@ this.login = login; this.user_id = result[0]; this.session = result[1]; + this.bus_url_host = result[2]; this.store(); this.renew_device_cookie(); dfd.resolve(); }, () => { this.user_id = null; this.session = null; + this.bus_url_host = null; this.store(); dfd.reject(); }); @@ -148,6 +151,7 @@ this.login = session_data.login; this.user_id = session_data.user_id; this.session = session_data.session; + this.bus_url_host = session_data.bus_url_host; } } } @@ -157,6 +161,7 @@ 'login': this.login, 'user_id': this.user_id, 'session': this.session, + 'bus_url_host': this.bus_url_host, }; session = JSON.stringify(session); localStorage.setItem('sao_session_' + this.database, session); diff --git a/tryton/tryton/bus.py b/tryton/tryton/bus.py index b1944c4c00..20d457c376 100644 --- a/tryton/tryton/bus.py +++ b/tryton/tryton/bus.py @@ -9,6 +9,7 @@ import time import uuid from collections import defaultdict from urllib.error import HTTPError +from urllib.parse import urljoin from urllib.request import Request, urlopen from gi.repository import GLib @@ -25,20 +26,25 @@ class Bus: current_thread = None channel_actions = defaultdict(list) listening = False + connection = None + host = None @classmethod - def listen(cls, connection): + def listen(cls, connection, host): if not CONFIG['thread']: return - listener = threading.Thread( - target=cls._listen, args=(connection,), daemon=True) + cls.connection = connection + cls.host = host + if not host: + return + listener = threading.Thread(target=cls._listen, daemon=True) listener.start() cls.current_thread = listener.ident @classmethod - def _listen(cls, connection): + def _listen(cls): bus_timeout = CONFIG['client.bus_timeout'] - session = connection.session + session = cls.connection.session authorization = base64.b64encode(session.encode('utf-8')) headers = { 'Content-Type': 'application/json', @@ -49,12 +55,12 @@ class Bus: wait = 1 last_message = None url = None - while connection.session == session: + while cls.connection.session == session: if url is None: - if connection.url is None: + if cls.connection._database is None: time.sleep(1) continue - url = connection.url + '/bus' + url = urljoin(cls.host, f'{cls.connection._database}/bus') cls.listening = True channels = list(cls.channel_actions) @@ -91,7 +97,7 @@ class Bus: wait *= 2 continue - if connection.session != session: + if cls.connection.session != session: break if thread_id != cls.current_thread: break @@ -109,14 +115,12 @@ class Bus: @classmethod def register(cls, channel, function): - from tryton import rpc - restart = channel not in cls.channel_actions cls.channel_actions[channel].append(function) if restart: # We can not really abort a thread, so we will just start a new one # and ignore the result of the one already running - Bus.listen(rpc.CONNECTION) + Bus.listen(cls.connection, cls.host) @classmethod def unregister(cls, channel, function): diff --git a/tryton/tryton/rpc.py b/tryton/tryton/rpc.py index e941862dac..6559e0aa67 100644 --- a/tryton/tryton/rpc.py +++ b/tryton/tryton/rpc.py @@ -102,12 +102,13 @@ def set_service_session(parameters): if _USER != renew_id: raise ValueError _USER = user_id + bus_url_host = parameters.get('bus_url_host', [''])[0] session = ':'.join(map(str, [username, user_id, session])) if CONNECTION is not None: CONNECTION.close() CONNECTION = ServerPool( hostname, port, database, session=session, cache=not CONFIG['dev']) - Bus.listen(CONNECTION) + Bus.listen(CONNECTION, bus_url_host) def login(parameters): @@ -126,13 +127,14 @@ def login(parameters): result = connection.common.db.login(username, parameters, language) logger.debug('%r', result) _USER = result[0] - session = ':'.join(map(str, [username] + result)) + session = ':'.join(map(str, [username] + result[:2])) + bus_url_host = result[2] if CONNECTION is not None: CONNECTION.close() CONNECTION = ServerPool( hostname, port, database, session=session, cache=not CONFIG['dev']) device_cookie.renew() - Bus.listen(CONNECTION) + Bus.listen(CONNECTION, bus_url_host) def logout(): diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index b139e02259..0058ea2397 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Handle URL host of the bus on login * Restrict sessions to the client's IP address * Use ``tryton.cfg`` to register in the Pool * Use the FILTER syntax with SQL aggregate functions diff --git a/trytond/trytond/bus.py b/trytond/trytond/bus.py index 938a8d3804..360e2299d1 100644 --- a/trytond/trytond/bus.py +++ b/trytond/trytond/bus.py @@ -9,13 +9,11 @@ import selectors import threading import time import uuid -from urllib.parse import urljoin from trytond import backend from trytond.config import config from trytond.protocols.jsonrpc import JSONDecoder, JSONEncoder -from trytond.protocols.wrappers import ( - HTTPStatus, Response, exceptions, redirect) +from trytond.protocols.wrappers import HTTPStatus, Response, abort, exceptions from trytond.tools import resolve from trytond.transaction import Transaction from trytond.wsgi import app @@ -231,12 +229,7 @@ def subscribe(request, database_name): if not _allow_subscribe: raise exceptions.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 + abort(HTTPStatus.UNAUTHORIZED) user = request.authorization.get('userid') channels = request.parsed_data.get('channels', []) if user is None: diff --git a/trytond/trytond/protocols/dispatcher.py b/trytond/trytond/protocols/dispatcher.py index e71374f4d2..4d97bf5897 100644 --- a/trytond/trytond/protocols/dispatcher.py +++ b/trytond/trytond/protocols/dispatcher.py @@ -62,7 +62,10 @@ def login(request, database_name, user, parameters, language=None): code = HTTPStatus.TOO_MANY_REQUESTS if not session: abort(code) - return session + allow_subscribe = config.getboolean( + 'bus', 'allow_subscribe', default=False) + bus_url_host = config.get('bus', 'url_host', default=request.host_url) + return (*session, bus_url_host if allow_subscribe else None) @app.auth_required