From 26c7af0dbf3b65cfe7d17e58cfe943eac7c00b99 Mon Sep 17 00:00:00 2001 From: Samuel Paccoud - DINUM Date: Sun, 6 Apr 2025 21:20:04 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(backend)=20add=20ancestors=20links=20?= =?UTF-8?q?definitions=20to=20document=20abilities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frontend needs to display inherited link accesses when it displays possible selection options. We need to return this information to the client. --- CHANGELOG.md | 1 + src/backend/core/models.py | 34 +++++++++++++------ .../documents/test_api_documents_retrieve.py | 12 +++++++ .../documents/test_api_documents_trashbin.py | 1 + .../core/tests/test_models_documents.py | 18 +++++++--- 5 files changed, 50 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51ace2f5..f1ce0771 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to ### Added +- ✨(backend) add ancestors links definitions to document abilities #846 - ✨(frontend) add customization for translations #857 - ✨(frontend) Duplicate a doc #1078 - 📝(project) add troubleshoot doc #1066 diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 160839be..6cdcea96 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -749,17 +749,16 @@ class Document(MP_Node, BaseModel): roles = [] return roles - def get_links_definitions(self, ancestors_links): - """Get links reach/role definitions for the current document and its ancestors.""" + def get_ancestors_links_definitions(self, ancestors_links): + """Get links reach/role definitions for ancestors of the current document.""" - links_definitions = defaultdict(set) - links_definitions[self.link_reach].add(self.link_role) - - # Merge ancestor link definitions + ancestors_links_definitions = defaultdict(set) for ancestor in ancestors_links: - links_definitions[ancestor["link_reach"]].add(ancestor["link_role"]) + ancestors_links_definitions[ancestor["link_reach"]].add( + ancestor["link_role"] + ) - return dict(links_definitions) # Convert default dict back to a normal dict + return ancestors_links_definitions def compute_ancestors_links(self, user): """ @@ -815,10 +814,20 @@ class Document(MP_Node, BaseModel): ) and not is_deleted # Add roles provided by the document link, taking into account its ancestors - links_definitions = self.get_links_definitions(ancestors_links) - public_roles = links_definitions.get(LinkReachChoices.PUBLIC, set()) + ancestors_links_definitions = self.get_ancestors_links_definitions( + ancestors_links + ) + + public_roles = ancestors_links_definitions.get( + LinkReachChoices.PUBLIC, set() + ) | ({self.link_role} if self.link_reach == LinkReachChoices.PUBLIC else set()) authenticated_roles = ( - links_definitions.get(LinkReachChoices.AUTHENTICATED, set()) + ancestors_links_definitions.get(LinkReachChoices.AUTHENTICATED, set()) + | ( + {self.link_role} + if self.link_reach == LinkReachChoices.AUTHENTICATED + else set() + ) if user.is_authenticated else set() ) @@ -864,6 +873,9 @@ class Document(MP_Node, BaseModel): "restore": is_owner, "retrieve": can_get, "media_auth": can_get, + "ancestors_links_definitions": { + k: list(v) for k, v in ancestors_links_definitions.items() + }, "link_select_options": LinkReachChoices.get_select_options(ancestors_links), "tree": can_get, "update": can_update, diff --git a/src/backend/core/tests/documents/test_api_documents_retrieve.py b/src/backend/core/tests/documents/test_api_documents_retrieve.py index 81ae8756..06d79e4c 100644 --- a/src/backend/core/tests/documents/test_api_documents_retrieve.py +++ b/src/backend/core/tests/documents/test_api_documents_retrieve.py @@ -43,6 +43,7 @@ def test_api_documents_retrieve_anonymous_public_standalone(): "favorite": False, "invite_owner": False, "link_configuration": False, + "ancestors_links_definitions": {}, "link_select_options": { "authenticated": ["reader", "editor"], "public": ["reader", "editor"], @@ -99,6 +100,10 @@ def test_api_documents_retrieve_anonymous_public_parent(): "accesses_view": False, "ai_transform": False, "ai_translate": False, + "ancestors_links_definitions": { + "public": [grand_parent.link_role], + parent.link_reach: [parent.link_role], + }, "attachment_upload": grand_parent.link_role == "editor", "can_edit": grand_parent.link_role == "editor", "children_create": False, @@ -197,6 +202,7 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated( "accesses_view": False, "ai_transform": document.link_role == "editor", "ai_translate": document.link_role == "editor", + "ancestors_links_definitions": {}, "attachment_upload": document.link_role == "editor", "can_edit": document.link_role == "editor", "children_create": document.link_role == "editor", @@ -273,6 +279,10 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea "accesses_view": False, "ai_transform": grand_parent.link_role == "editor", "ai_translate": grand_parent.link_role == "editor", + "ancestors_links_definitions": { + grand_parent.link_reach: [grand_parent.link_role], + "restricted": [parent.link_role], + }, "attachment_upload": grand_parent.link_role == "editor", "can_edit": grand_parent.link_role == "editor", "children_create": grand_parent.link_role == "editor", @@ -448,6 +458,7 @@ def test_api_documents_retrieve_authenticated_related_parent(): ) assert response.status_code == 200 links = document.get_ancestors().values("link_reach", "link_role") + ancestors_roles = list({grand_parent.link_role, parent.link_role}) assert response.json() == { "id": str(document.id), "abilities": { @@ -455,6 +466,7 @@ def test_api_documents_retrieve_authenticated_related_parent(): "accesses_view": True, "ai_transform": access.role != "reader", "ai_translate": access.role != "reader", + "ancestors_links_definitions": {"restricted": ancestors_roles}, "attachment_upload": access.role != "reader", "can_edit": access.role != "reader", "children_create": access.role != "reader", diff --git a/src/backend/core/tests/documents/test_api_documents_trashbin.py b/src/backend/core/tests/documents/test_api_documents_trashbin.py index 77e67b81..91cdf073 100644 --- a/src/backend/core/tests/documents/test_api_documents_trashbin.py +++ b/src/backend/core/tests/documents/test_api_documents_trashbin.py @@ -74,6 +74,7 @@ def test_api_documents_trashbin_format(): "accesses_view": True, "ai_transform": True, "ai_translate": True, + "ancestors_links_definitions": {}, "attachment_upload": True, "can_edit": True, "children_create": True, diff --git a/src/backend/core/tests/test_models_documents.py b/src/backend/core/tests/test_models_documents.py index af001a1a..3e73cb16 100644 --- a/src/backend/core/tests/test_models_documents.py +++ b/src/backend/core/tests/test_models_documents.py @@ -154,6 +154,7 @@ def test_models_documents_get_abilities_forbidden( "accesses_view": False, "ai_transform": False, "ai_translate": False, + "ancestors_links_definitions": {}, "attachment_upload": False, "can_edit": False, "children_create": False, @@ -216,6 +217,7 @@ def test_models_documents_get_abilities_reader( "accesses_view": False, "ai_transform": False, "ai_translate": False, + "ancestors_links_definitions": {}, "attachment_upload": False, "can_edit": False, "children_create": False, @@ -254,7 +256,7 @@ def test_models_documents_get_abilities_reader( assert all( value is False for key, value in document.get_abilities(user).items() - if key != "link_select_options" + if key not in ["link_select_options", "ancestors_links_definitions"] ) @@ -280,6 +282,7 @@ def test_models_documents_get_abilities_editor( "accesses_view": False, "ai_transform": is_authenticated, "ai_translate": is_authenticated, + "ancestors_links_definitions": {}, "attachment_upload": True, "can_edit": True, "children_create": is_authenticated, @@ -317,7 +320,7 @@ def test_models_documents_get_abilities_editor( assert all( value is False for key, value in document.get_abilities(user).items() - if key != "link_select_options" + if key not in ["link_select_options", "ancestors_links_definitions"] ) @@ -333,6 +336,7 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries): "accesses_view": True, "ai_transform": True, "ai_translate": True, + "ancestors_links_definitions": {}, "attachment_upload": True, "can_edit": True, "children_create": True, @@ -383,6 +387,7 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries) "accesses_view": True, "ai_transform": True, "ai_translate": True, + "ancestors_links_definitions": {}, "attachment_upload": True, "can_edit": True, "children_create": True, @@ -420,7 +425,7 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries) assert all( value is False for key, value in document.get_abilities(user).items() - if key != "link_select_options" + if key not in ["link_select_options", "ancestors_links_definitions"] ) @@ -436,6 +441,7 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries): "accesses_view": True, "ai_transform": True, "ai_translate": True, + "ancestors_links_definitions": {}, "attachment_upload": True, "can_edit": True, "children_create": True, @@ -473,7 +479,7 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries): assert all( value is False for key, value in document.get_abilities(user).items() - if key != "link_select_options" + if key not in ["link_select_options", "ancestors_links_definitions"] ) @@ -496,6 +502,7 @@ def test_models_documents_get_abilities_reader_user( # You should not access AI if it's restricted to users with specific access "ai_transform": access_from_link and ai_access_setting != "restricted", "ai_translate": access_from_link and ai_access_setting != "restricted", + "ancestors_links_definitions": {}, "attachment_upload": access_from_link, "can_edit": access_from_link, "children_create": access_from_link, @@ -535,7 +542,7 @@ def test_models_documents_get_abilities_reader_user( assert all( value is False for key, value in document.get_abilities(user).items() - if key != "link_select_options" + if key not in ["link_select_options", "ancestors_links_definitions"] ) @@ -554,6 +561,7 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries): "accesses_view": True, "ai_transform": False, "ai_translate": False, + "ancestors_links_definitions": {}, "attachment_upload": False, "can_edit": False, "children_create": False,