From f4ef13a485fbfbf5d77c471e3a38f9e8af0b87d0 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Tue, 11 Jun 2024 14:43:46 +0200 Subject: [PATCH] [FIX] website_require_login: Login recursion If one of the parents of /web/login is requested for login, infinite redirection loop starts --- website_require_login/models/ir_http.py | 19 ++++++++++- website_require_login/tests/test_ir_http.py | 37 +++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/website_require_login/models/ir_http.py b/website_require_login/models/ir_http.py index 1cc37285cb..7cb3b7c068 100644 --- a/website_require_login/models/ir_http.py +++ b/website_require_login/models/ir_http.py @@ -24,6 +24,16 @@ def _serve_fallback(cls): return res return super()._serve_fallback() + @classmethod + def _require_login_whitelist_paths(cls): + """List of paths that must always be available to all users.""" + return [ + # backend is already protected by login, + # also /web/login, /web/assets, /web/image and others + # are needed to correctly render the login page + "/web", + ] + @classmethod def _require_login_get_matching_path(cls, path, search_paths): """Return which one of `search_paths` is a parent of `path`.""" @@ -42,6 +52,14 @@ def _check_require_auth(cls): website = request.env["website"].sudo().get_current_website() if not website: return None + + # Skip whitelisted paths + path = request.httprequest.path + whitelist_paths = cls._require_login_whitelist_paths() + whitelist_path = cls._require_login_get_matching_path(path, whitelist_paths) + if whitelist_path: + return None + if request.uid and (request.uid != website.user_id.id): return None auth_paths = ( @@ -54,7 +72,6 @@ def _check_require_auth(cls): ) .mapped("path") ) - path = request.httprequest.path auth_path = cls._require_login_get_matching_path(path, auth_paths) if auth_path: redirect_path = "/web/login?redirect=%s" % path diff --git a/website_require_login/tests/test_ir_http.py b/website_require_login/tests/test_ir_http.py index ed172189b4..503964caa4 100644 --- a/website_require_login/tests/test_ir_http.py +++ b/website_require_login/tests/test_ir_http.py @@ -1,3 +1,6 @@ +# Copyright 2024 Simone Rubino - Aion Tech +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0). + from odoo.tests import HttpCase @@ -35,3 +38,37 @@ def test_dispatch_authorized(self): 200, "Expected the response status code to be 200 which means no redirection", ) + + def test_authorize_everything(self): + """Requiring "/" for authorization always redirects to login page.""" + # Arrange + self.env["website.auth.url"].unlink() + root_path = "/" + self.env["website.auth.url"].create( + {"website_id": self.website.id, "path": root_path} + ) + self.env["ir.qweb"]._pregenerate_assets_bundles() + asset_attachment = self.env["ir.attachment"].search( + [ + ("url", "like", "/web/assets/%"), + ], + limit=1, + ) + + redirection_path_map = { + "/": "/web/login?redirect=/", + "/contactus": "/web/login?redirect=/contactus", + asset_attachment.url: asset_attachment.url, + "/web/login": "/web/login", + } + + # Assert + base_url = self.base_url() + for requested_path, expected_redirected_path in redirection_path_map.items(): + response = self.url_open(requested_path) + self.assertEqual( + response.status_code, + 200, + ) + response_path = response.url.replace(base_url, "") + self.assertEqual(response_path, expected_redirected_path)