Files
odoo_source/odoo/addons/test_http/tests/test_session.py
T
Julien Castiaux ec4826ef5b [FIX] core: session logout after 16.0 migration
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>
2022-11-15 18:25:40 +01:00

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')