From 4380b5d56337f4c224f921507688d39b23c2200f Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Tue, 11 Jun 2024 14:47:22 +0200 Subject: [PATCH 1/3] [REF] website_require_login: Extract path matching Also create only one instance of Path(path) --- website_require_login/README.rst | 3 +++ website_require_login/models/ir_http.py | 21 +++++++++++++++---- website_require_login/readme/CONTRIBUTORS.rst | 3 +++ .../static/description/index.html | 5 ++++- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/website_require_login/README.rst b/website_require_login/README.rst index adcca320a6..d5f64759c5 100644 --- a/website_require_login/README.rst +++ b/website_require_login/README.rst @@ -65,6 +65,9 @@ Contributors ~~~~~~~~~~~~ * Ooops404 +* `Aion Tech `_: + + * Simone Rubino Maintainers ~~~~~~~~~~~ diff --git a/website_require_login/models/ir_http.py b/website_require_login/models/ir_http.py index bcfea63b96..1cc37285cb 100644 --- a/website_require_login/models/ir_http.py +++ b/website_require_login/models/ir_http.py @@ -1,4 +1,5 @@ # Copyright 2020 Advitus MB +# Copyright 2024 Simone Rubino - Aion Tech # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0). from pathlib import Path @@ -23,6 +24,18 @@ def _serve_fallback(cls): return res return super()._serve_fallback() + @classmethod + def _require_login_get_matching_path(cls, path, search_paths): + """Return which one of `search_paths` is a parent of `path`.""" + path_inst = Path(path) + for search_path in search_paths: + if search_path == path or Path(search_path) in path_inst.parents: + matching_path = search_path + break + else: + matching_path = None + return matching_path + @classmethod def _check_require_auth(cls): # if not website request - skip @@ -42,7 +55,7 @@ def _check_require_auth(cls): .mapped("path") ) path = request.httprequest.path - for auth_path in auth_paths: - if auth_path == path or Path(auth_path) in Path(path).parents: - redirect_path = "/web/login?redirect=%s" % path - return request.redirect(redirect_path, code=302) + auth_path = cls._require_login_get_matching_path(path, auth_paths) + if auth_path: + redirect_path = "/web/login?redirect=%s" % path + return request.redirect(redirect_path, code=302) diff --git a/website_require_login/readme/CONTRIBUTORS.rst b/website_require_login/readme/CONTRIBUTORS.rst index b5f195b844..5e5c4c6059 100644 --- a/website_require_login/readme/CONTRIBUTORS.rst +++ b/website_require_login/readme/CONTRIBUTORS.rst @@ -1 +1,4 @@ * Ooops404 +* `Aion Tech `_: + + * Simone Rubino diff --git a/website_require_login/static/description/index.html b/website_require_login/static/description/index.html index 3d6268bacf..8e72f43c4b 100644 --- a/website_require_login/static/description/index.html +++ b/website_require_login/static/description/index.html @@ -1,4 +1,3 @@ - @@ -410,6 +409,10 @@

Authors

Contributors

From 7264b78845804a7c7f3230571dd68271c7b2d61e Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Tue, 11 Jun 2024 14:43:46 +0200 Subject: [PATCH 2/3] [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/README.rst | 2 +- website_require_login/models/ir_http.py | 20 +++++++++++- .../static/description/index.html | 13 +++++--- website_require_login/tests/test_ir_http.py | 31 +++++++++++++++++++ 4 files changed, 59 insertions(+), 7 deletions(-) diff --git a/website_require_login/README.rst b/website_require_login/README.rst index d5f64759c5..2cdae0899b 100644 --- a/website_require_login/README.rst +++ b/website_require_login/README.rst @@ -7,7 +7,7 @@ Website Login Required !! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !! source digest: sha256:a0f16f0655f6b5b9eec2c2e621f35dbcc40c69a3c5353dec8b07ddfab1874313 + !! source digest: sha256:74ad683eba54803e4df9ebfe80d38fba6520b41c3d7ce6106be4aad28f2b2726 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png diff --git a/website_require_login/models/ir_http.py b/website_require_login/models/ir_http.py index 1cc37285cb..455f15851f 100644 --- a/website_require_login/models/ir_http.py +++ b/website_require_login/models/ir_http.py @@ -24,6 +24,17 @@ 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", + "/website/translations", + ] + @classmethod def _require_login_get_matching_path(cls, path, search_paths): """Return which one of `search_paths` is a parent of `path`.""" @@ -42,6 +53,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 +73,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/static/description/index.html b/website_require_login/static/description/index.html index 8e72f43c4b..9408626de3 100644 --- a/website_require_login/static/description/index.html +++ b/website_require_login/static/description/index.html @@ -8,10 +8,11 @@ /* :Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ +:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $ :Copyright: This stylesheet has been placed in the public domain. Default cascading style sheet for the HTML output of Docutils. +Despite the name, some widely supported CSS2 features are used. See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to customize this style sheet. @@ -274,7 +275,7 @@ margin-left: 2em ; margin-right: 2em } -pre.code .ln { color: grey; } /* line numbers */ +pre.code .ln { color: gray; } /* line numbers */ pre.code, code { background-color: #eeeeee } pre.code .comment, code .comment { color: #5C6576 } pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } @@ -300,7 +301,7 @@ span.pre { white-space: pre } -span.problematic { +span.problematic, pre.problematic { color: red } span.section-subtitle { @@ -366,7 +367,7 @@

Website Login Required

!! This file is generated by oca-gen-addon-readme !! !! changes will be overwritten. !! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -!! source digest: sha256:a0f16f0655f6b5b9eec2c2e621f35dbcc40c69a3c5353dec8b07ddfab1874313 +!! source digest: sha256:74ad683eba54803e4df9ebfe80d38fba6520b41c3d7ce6106be4aad28f2b2726 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->

Beta License: LGPL-3 OCA/website Translate me on Weblate Try me on Runboat

This module allows to restrict access to specific website pages to logged users.

@@ -418,7 +419,9 @@

Contributors

Maintainers

This module is maintained by the OCA.

-Odoo Community Association + +Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose mission is to support the collaborative development of Odoo features and promote its widespread use.

diff --git a/website_require_login/tests/test_ir_http.py b/website_require_login/tests/test_ir_http.py index ed172189b4..61a6cbbbe8 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,31 @@ 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 + for requested_path, expected_redirected_path in redirection_path_map.items(): + response = self.url_open(requested_path) + self.assertTrue(response.url.endswith(expected_redirected_path)) From 958f8029a08a781f042428892986920a856430c3 Mon Sep 17 00:00:00 2001 From: Simone Rubino Date: Mon, 5 Aug 2024 11:54:14 +0200 Subject: [PATCH 3/3] [FIX] website_require_login: Allow RPC JSON/XML RPC should not be redirected to login. --- website_require_login/models/ir_http.py | 2 ++ website_require_login/tests/test_ir_http.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/website_require_login/models/ir_http.py b/website_require_login/models/ir_http.py index 455f15851f..b0b0964770 100644 --- a/website_require_login/models/ir_http.py +++ b/website_require_login/models/ir_http.py @@ -33,6 +33,8 @@ def _require_login_whitelist_paths(cls): # are needed to correctly render the login page "/web", "/website/translations", + "/jsonrpc", + "/xmlrpc", ] @classmethod diff --git a/website_require_login/tests/test_ir_http.py b/website_require_login/tests/test_ir_http.py index 61a6cbbbe8..9de37182ca 100644 --- a/website_require_login/tests/test_ir_http.py +++ b/website_require_login/tests/test_ir_http.py @@ -60,6 +60,9 @@ def test_authorize_everything(self): "/contactus": "/web/login?redirect=/contactus", asset_attachment.url: asset_attachment.url, "/web/login": "/web/login", + "/jsonrpc": "/jsonrpc", + "/xmlrpc/2/common": "/xmlrpc/2/common", + "/xmlrpc/2/object": "/xmlrpc/2/object", } # Assert