From ca5ca24bc6f58204b65032e974d39b6fd413b934 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Thu, 23 Mar 2023 14:14:20 +0000 Subject: [PATCH] [FIX] core: check browser lang is installed A visitor could visit a web page having a lang in its context that is not installed in the databased he is connected to. The problem is that visitors are not logged-in thus it is not possible to determine their lang via their `res.users` preferences. The lang used instead is the lang set in the `Accept-Language` header of the incoming request, that header is set by various browsers in accordance to the user system preferences or browser settings. The browser lang (`Request.best`) is only parsed according to the `babel` database, it is a lang syntactically speaking but not necessary a lang that is installed in the database. At the moment the browser lang is set in the context (inside of `Request._get_dbname_and_session`), it is not possible to verify it is installed in the database as no connection to any database as been established yet. Instead the lang is validated inside of `ir.http._pre_dispatch` which is the method responsible to prepare/fix various stuff on the request/session/context. In regard to 93b684d3c784, we prefer to fallback on English, hence the modification in `get_lang`. closes odoo/odoo#116683 Reference-to: 93b684d3c784 ([FIX] base: lang should fallback on english instead of arab) X-original-commit: 743e97667e44cdaab6db334d807cf3d409cea621 Signed-off-by: Julien Castiaux (juc) --- odoo/addons/base/models/ir_http.py | 7 +++- odoo/addons/test_http/controllers.py | 4 ++ odoo/addons/test_http/tests/test_session.py | 43 +++++++++++++++++++++ odoo/tools/misc.py | 4 +- 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/odoo/addons/base/models/ir_http.py b/odoo/addons/base/models/ir_http.py index b89a846110b..1dbb94cca5b 100644 --- a/odoo/addons/base/models/ir_http.py +++ b/odoo/addons/base/models/ir_http.py @@ -29,7 +29,7 @@ from odoo.exceptions import AccessDenied, AccessError, MissingError from odoo.http import request, Response, ROUTING_KEYS, Stream from odoo.modules.registry import Registry from odoo.service import security -from odoo.tools import consteq, submap +from odoo.tools import get_lang, submap from odoo.tools.translate import code_translations from odoo.modules.module import get_resource_path, get_module_path @@ -144,6 +144,11 @@ class IrHttp(models.AbstractModel): if isinstance(val, models.BaseModel) and isinstance(val._uid, RequestUID): args[key] = val.with_user(request.env.uid) + # verify the default language set in the context is valid, + # otherwise fallback on the company lang, english or the first + # lang installed + request.update_context(lang=get_lang(request.env)._get_cached('code')) + @classmethod def _dispatch(cls, endpoint): result = endpoint(**request.params) diff --git a/odoo/addons/test_http/controllers.py b/odoo/addons/test_http/controllers.py index 242174ab1bc..9933c0862f6 100644 --- a/odoo/addons/test_http/controllers.py +++ b/odoo/addons/test_http/controllers.py @@ -67,6 +67,10 @@ class TestHttp(http.Controller): def echo_http_csrf(self, **kwargs): return str(kwargs) + @http.route('/test_http/echo-http-context-lang', type='http', auth='public', methods=['GET'], csrf=False) + def echo_http_context_lang(self, **kwargs): + return request.env.context.get('lang', '') + @http.route('/test_http/echo-json', type='json', auth='none', methods=['POST'], csrf=False) def echo_json(self, **kwargs): return kwargs diff --git a/odoo/addons/test_http/tests/test_session.py b/odoo/addons/test_http/tests/test_session.py index 6a0ea900745..647dc744a8c 100644 --- a/odoo/addons/test_http/tests/test_session.py +++ b/odoo/addons/test_http/tests/test_session.py @@ -88,3 +88,46 @@ class TestHttpSession(TestHttpBase): res = self.multidb_url_open('/test_http/greeting-user') res.raise_for_status() self.assertEqual(res.status_code, 200, "Should not be redirected to /web/login") + + def test_session5_default_lang(self): + self.env['res.lang']._activate_lang('en_US') # default lang + lang_fr = self.env['res.lang']._activate_lang('fr_FR') + + with self.subTest(case='no preferred lang'): + res = self.url_open('/test_http/echo-http-context-lang') + self.assertEqual(res.text, 'en_US') + + with self.subTest(case='fr preferred and fr_FR enabled'): + res = self.url_open('/test_http/echo-http-context-lang', headers={ + 'Accept-Language': 'fr', + }) + self.assertEqual(res.text, 'fr_FR') + + with self.subTest(case='fr preferred but fr_FR disabled'): + lang_fr.active = False + res = self.url_open('/test_http/echo-http-context-lang', headers={ + 'Accept-Language': 'fr', + }) + self.assertEqual(res.text, 'en_US') + + def test_session6_saved_lang(self): + session = self.authenticate('demo', 'demo') + self.env['res.lang']._activate_lang('en_US') # default lang + lang_fr = self.env['res.lang']._activate_lang('fr_FR') + + with self.subTest(case='no saved lang'): + res = self.url_open('/test_http/echo-http-context-lang') + self.assertEqual(res.text, 'en_US') + + with self.subTest(case='fr saved and fr_FR enabled'): + session.context['lang'] = 'fr_FR' + odoo.http.root.session_store.save(session) + res = self.url_open('/test_http/echo-http-context-lang') + self.assertEqual(res.text, 'fr_FR') + + with self.subTest(case='fr saved but fr_FR disabled'): + session['lang'] = 'fr_FR' + odoo.http.root.session_store.save(session) + lang_fr.active = False + res = self.url_open('/test_http/echo-http-context-lang') + self.assertEqual(res.text, 'en_US') diff --git a/odoo/tools/misc.py b/odoo/tools/misc.py index 3b2b0b95656..979ea936cbf 100644 --- a/odoo/tools/misc.py +++ b/odoo/tools/misc.py @@ -1312,14 +1312,14 @@ def get_lang(env, lang_code=False): """ Retrieve the first lang object installed, by checking the parameter lang_code, the context and then the company. If no lang is installed from those variables, - fallback on the first lang installed in the system. + fallback on english or on the first lang installed in the system. :param env: :param str lang_code: the locale (i.e. en_US) :return res.lang: the first lang found that is installed on the system. """ langs = [code for code, _ in env['res.lang'].get_installed()] - lang = langs[0] + lang = 'en_US' if 'en_US' in langs else langs[0] if lang_code and lang_code in langs: lang = lang_code elif env.context.get('lang') in langs: