Handle URL host of the bus on login instead of using a redirect
Browsers remove authorization header upon cross-origin redirect. Closes #12329
This commit is contained in:
@@ -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))
|
||||
|
||||
+3
-2
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+16
-12
@@ -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):
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user