Create a 15.0 database with website, access the home page via your
browser. Stop the server and migrate the database to 16.0. Restart the
server with a `--dbfilter` that rejects the database you created and
refresh your browser. 500 Internal server error, attribute error:
the `request` object as no `session`.
An error could occurs after a migration to 16.0 due to the presence of
the `geoip` key in the session. `request.session.geoip` has been made a
deprecated alias to `request.geoip` between 15.0 and 16.0, see 04e9726.
Because the session was created before 16.0, the session dict does
contain a `geoip` key. Upon logging the session out, the session dict
is cleared. The default implementation of `clear()`[^1] inside of
`collections.abc.MutableMapping` can be summarized for our usecase to:
for key in self:
value = self[key]
del self[key]
There is an extra `__getitem__` call due to `value = self[key]`, in the
case of the `geoip` key, it would access the alias. It is not possible
to accessing that alias inside of the `_get_dbname_and_session` method
of request as the session has not been set on `self` (the request) yet.
Yet inside of that method, we do `session.logout()` which `clear()` the
session which (wrongly) access the alias because `geoip` exists in the
internal dict (`'geoip' in self.keys() # True`).
The solution has been to implement the `clear()` function ourself
instead of using the mixin of `MutableMapping`.
[^1]: https://github.com/python/cpython/blob/b43496c01a554cf41ae654a0379efae18609ad39/Lib/_collections_abc.py#L925-L931
closes odoo/odoo#105763
X-original-commit: b66e1ffa8e348eedf2de735babbc398290a8bffb
Signed-off-by: Julien Castiaux <juc@odoo.com>
94 lines
3.8 KiB
Python
94 lines
3.8 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from urllib.parse import urlparse
|
|
from unittest.mock import patch
|
|
|
|
import odoo
|
|
from odoo.tools import mute_logger
|
|
from .test_common import TestHttpBase
|
|
|
|
|
|
GEOIP_ODOO_FARM_2 = {
|
|
'city': 'Ramillies',
|
|
'country_code': 'BE',
|
|
'country_name': 'Belgium',
|
|
'latitude': 50.6314,
|
|
'longitude': 4.8573,
|
|
'region': 'WAL',
|
|
'time_zone': 'Europe/Brussels'
|
|
}
|
|
|
|
|
|
class TestHttpSession(TestHttpBase):
|
|
|
|
@mute_logger('odoo.http') # greeting_none called ignoring args {'debug'}
|
|
def test_session0_debug_mode(self):
|
|
session = self.authenticate(None, None)
|
|
self.assertEqual(session.debug, '')
|
|
self.db_url_open('/test_http/greeting').raise_for_status()
|
|
self.assertEqual(session.debug, '')
|
|
self.db_url_open('/test_http/greeting?debug=1').raise_for_status()
|
|
self.assertEqual(session.debug, '1')
|
|
self.db_url_open('/test_http/greeting').raise_for_status()
|
|
self.assertEqual(session.debug, '1')
|
|
self.db_url_open('/test_http/greeting?debug=').raise_for_status()
|
|
self.assertEqual(session.debug, '')
|
|
|
|
def test_session1_default_session(self):
|
|
# The default session should not be saved on the filestore.
|
|
with patch.object(odoo.http.root.session_store, 'save') as mock_save:
|
|
res = self.db_url_open('/test_http/greeting')
|
|
res.raise_for_status()
|
|
try:
|
|
mock_save.assert_not_called()
|
|
except AssertionError as exc:
|
|
msg = f'save() was called with args: {mock_save.call_args}'
|
|
raise AssertionError(msg) from exc
|
|
|
|
def test_session2_geoip(self):
|
|
real_save = odoo.http.root.session_store.save
|
|
with patch.object(odoo.http.root.geoip_resolver, 'resolve') as mock_resolve,\
|
|
patch.object(odoo.http.root.session_store, 'save') as mock_save:
|
|
mock_resolve.return_value = GEOIP_ODOO_FARM_2
|
|
mock_save.side_effect = real_save
|
|
|
|
# Geoip is lazy: it should be computed only when necessary.
|
|
self.nodb_url_open('/test_http/greeting').raise_for_status()
|
|
mock_resolve.assert_not_called()
|
|
|
|
# Geoip is like the defaut session: the session should not
|
|
# be stored only due to geoip.
|
|
mock_resolve.reset_mock()
|
|
mock_save.reset_mock()
|
|
res = self.nodb_url_open('/test_http/geoip')
|
|
res.raise_for_status()
|
|
self.assertEqual(res.text, str(GEOIP_ODOO_FARM_2))
|
|
mock_save.assert_not_called()
|
|
|
|
# Geoip is cached on the session: we shouldn't geolocate the
|
|
# same ip multiple times.
|
|
mock_resolve.reset_mock()
|
|
mock_save.reset_mock()
|
|
self.nodb_url_open('/test_http/save_session').raise_for_status()
|
|
self.nodb_url_open('/test_http/geoip').raise_for_status()
|
|
res = self.nodb_url_open('/test_http/geoip')
|
|
res.raise_for_status()
|
|
self.assertEqual(res.text, str(GEOIP_ODOO_FARM_2))
|
|
mock_resolve.assert_called_once()
|
|
|
|
def test_session3_logout_15_0_geoip(self):
|
|
session = self.authenticate(None, None)
|
|
session['db'] = 'idontexist'
|
|
session['geoip'] = {} # Until saas-15.2 geoip was directly stored in the session
|
|
odoo.http.root.session_store.save(session)
|
|
|
|
with self.assertLogs('odoo.http', level='WARNING') as (_, warnings):
|
|
res = self.multidb_url_open('/test_http/ensure_db', dblist=['db1', 'db2'])
|
|
|
|
self.assertEqual(warnings, [
|
|
"WARNING:odoo.http:Logged into database 'idontexist', but dbfilter rejects it; logging session out.",
|
|
])
|
|
self.assertFalse(session['db'])
|
|
self.assertEqual(res.status_code, 303)
|
|
self.assertEqual(urlparse(res.headers['Location']).path, '/web/database/selector')
|