diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 38edd5ac6ad..7da7a235376 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -24,9 +24,11 @@ from typing import Any, Callable, TYPE_CHECKING import wtforms_json from deprecation import deprecated -from flask import Flask, redirect, url_for +from flask import abort, Flask, redirect, request, session, url_for from flask_appbuilder import expose, IndexView -from flask_babel import gettext as __ +from flask_appbuilder.api import safe +from flask_appbuilder.utils.base import get_safe_redirect +from flask_babel import gettext as __, refresh from flask_compress import Compress from flask_session import Session from werkzeug.middleware.proxy_fix import ProxyFix @@ -717,3 +719,24 @@ class SupersetIndexView(IndexView): @expose("/") def index(self) -> FlaskResponse: return redirect(url_for("Superset.welcome")) + + @expose("/lang/") + @safe + def patch_flask_locale(self, locale: str) -> FlaskResponse: + """ + Change user's locale and redirect back to the previous page. + + Overrides FAB's babel.views.LocaleView so we can use the request + Referrer as the redirect target, in case our previous page was actually + served by the frontend (and thus not added to the session's page_history + stack). + """ + if locale not in self.appbuilder.bm.languages: + abort(404, description="Locale not supported.") + session["locale"] = locale + refresh() + self.update_redirect() + + if redirect_to := request.headers.get("Referer"): + return redirect(get_safe_redirect(redirect_to)) + return redirect(self.get_redirect()) diff --git a/tests/integration_tests/core_tests.py b/tests/integration_tests/core_tests.py index 84fb7f20dfd..d835103b062 100644 --- a/tests/integration_tests/core_tests.py +++ b/tests/integration_tests/core_tests.py @@ -872,5 +872,56 @@ class TestCore(SupersetTestCase): assert resp.status_code == 302 +class TestLocalePatch(SupersetTestCase): + MOCK_LANGUAGES = ( + "superset.views.filters.current_app.config", + { + "LANGUAGES": { + "es": {"flag": "es", "name": "EspaƱol"}, + }, + }, + ) + + @mock.patch.dict(*MOCK_LANGUAGES) + def test_lang_redirect(self): + self.login(GAMMA_USERNAME) + referer_url = "http://localhost/explore/" + resp = self.client.get("/lang/es", headers={"Referer": referer_url}) + + assert resp.status_code == 302 + assert resp.headers["Location"] == referer_url + with self.client.session_transaction() as session: + assert session["locale"] == "es" + + @mock.patch.dict(*MOCK_LANGUAGES) + def test_lang_invalid_referer(self): + self.login(GAMMA_USERNAME) + referer_url = "http://someotherserver/explore/" + resp = self.client.get("/lang/es", headers={"Referer": referer_url}) + + assert resp.status_code == 302 + assert resp.headers["Location"] == "/" + with self.client.session_transaction() as session: + assert session["locale"] == "es" + + @mock.patch.dict(*MOCK_LANGUAGES) + def test_lang_no_referer(self): + self.login(GAMMA_USERNAME) + resp = self.client.get("/lang/es") + + assert resp.status_code == 302 + assert resp.headers["Location"] == "/" + with self.client.session_transaction() as session: + assert session["locale"] == "es" + + def test_lang_invalid_locale(self): + self.login(GAMMA_USERNAME) + resp = self.client.get("/lang/es") + + assert resp.status_code == 500 + with self.client.session_transaction() as session: + assert session["locale"] == "en" + + if __name__ == "__main__": unittest.main() diff --git a/tests/integration_tests/security_tests.py b/tests/integration_tests/security_tests.py index 96886fab3b2..acb77b8412b 100644 --- a/tests/integration_tests/security_tests.py +++ b/tests/integration_tests/security_tests.py @@ -1550,6 +1550,7 @@ class TestRolePermission(SupersetTestCase): ["SecurityApi", "login"], ["SecurityApi", "refresh"], ["SupersetIndexView", "index"], + ["SupersetIndexView", "patch_flask_locale"], ["DatabaseRestApi", "oauth2"], ] unsecured_views = []