(backend) limit link reach/role select options depending on ancestors

If a document already gets a link reach/role inheriting from one of its
ancestors, we should not propose setting link reach/role on the
document that would be more restrictive than what we inherited from
ancestors.
This commit is contained in:
Samuel Paccoud - DINUM
2025-02-18 08:32:49 +01:00
committed by Manuel Raynaud
parent 2203d49a52
commit 20315e9b60
5 changed files with 284 additions and 17 deletions

View File

@@ -40,6 +40,11 @@ def test_api_documents_retrieve_anonymous_public_standalone():
"favorite": False,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": False,
"partial_update": document.link_role == "editor",
@@ -81,6 +86,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/")
assert response.status_code == 200
links = document.get_ancestors().values("link_reach", "link_role")
assert response.json() == {
"id": str(document.id),
"abilities": {
@@ -98,6 +104,7 @@ def test_api_documents_retrieve_anonymous_public_parent():
"favorite": False,
"invite_owner": False,
"link_configuration": False,
"link_select_options": models.LinkReachChoices.get_select_options(links),
"media_auth": True,
"move": False,
"partial_update": grand_parent.link_role == "editor",
@@ -189,6 +196,11 @@ def test_api_documents_retrieve_authenticated_unrelated_public_or_authenticated(
"favorite": True,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": False,
"partial_update": document.link_role == "editor",
@@ -238,6 +250,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
response = client.get(f"/api/v1.0/documents/{document.id!s}/")
assert response.status_code == 200
links = document.get_ancestors().values("link_reach", "link_role")
assert response.json() == {
"id": str(document.id),
"abilities": {
@@ -254,6 +267,7 @@ def test_api_documents_retrieve_authenticated_public_or_authenticated_parent(rea
"favorite": True,
"invite_owner": False,
"link_configuration": False,
"link_select_options": models.LinkReachChoices.get_select_options(links),
"move": False,
"media_auth": True,
"partial_update": grand_parent.link_role == "editor",
@@ -412,6 +426,7 @@ def test_api_documents_retrieve_authenticated_related_parent():
f"/api/v1.0/documents/{document.id!s}/",
)
assert response.status_code == 200
links = document.get_ancestors().values("link_reach", "link_role")
assert response.json() == {
"id": str(document.id),
"abilities": {
@@ -428,6 +443,7 @@ def test_api_documents_retrieve_authenticated_related_parent():
"favorite": True,
"invite_owner": access.role == "owner",
"link_configuration": access.role in ["administrator", "owner"],
"link_select_options": models.LinkReachChoices.get_select_options(links),
"media_auth": True,
"move": access.role in ["administrator", "owner"],
"partial_update": access.role != "reader",

View File

@@ -83,6 +83,11 @@ def test_api_documents_trashbin_format():
"favorite": True,
"invite_owner": True,
"link_configuration": True,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": False, # Can't move a deleted document
"partial_update": True,

View File

@@ -164,6 +164,11 @@ def test_models_documents_get_abilities_forbidden(
"media_auth": False,
"move": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"partial_update": False,
"restore": False,
"retrieve": False,
@@ -215,6 +220,11 @@ def test_models_documents_get_abilities_reader(
"favorite": is_authenticated,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": False,
"partial_update": False,
@@ -229,9 +239,14 @@ def test_models_documents_get_abilities_reader(
nb_queries = 1 if is_authenticated else 0
with django_assert_num_queries(nb_queries):
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
assert all(
value is False
for key, value in document.get_abilities(user).items()
if key != "link_select_options"
)
@pytest.mark.parametrize(
@@ -265,6 +280,11 @@ def test_models_documents_get_abilities_editor(
"favorite": is_authenticated,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": False,
"partial_update": True,
@@ -281,7 +301,11 @@ def test_models_documents_get_abilities_editor(
assert document.get_abilities(user) == expected_abilities
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
assert all(
value is False
for key, value in document.get_abilities(user).items()
if key != "link_select_options"
)
@override_settings(
@@ -305,6 +329,11 @@ def test_models_documents_get_abilities_owner(django_assert_num_queries):
"favorite": True,
"invite_owner": True,
"link_configuration": True,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": True,
"partial_update": True,
@@ -346,6 +375,11 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
"favorite": True,
"invite_owner": False,
"link_configuration": True,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": True,
"partial_update": True,
@@ -362,7 +396,11 @@ def test_models_documents_get_abilities_administrator(django_assert_num_queries)
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
assert all(
value is False
for key, value in document.get_abilities(user).items()
if key != "link_select_options"
)
@override_settings(
@@ -386,6 +424,11 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
"favorite": True,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": False,
"partial_update": True,
@@ -402,7 +445,11 @@ def test_models_documents_get_abilities_editor_user(django_assert_num_queries):
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
assert all(
value is False
for key, value in document.get_abilities(user).items()
if key != "link_select_options"
)
@pytest.mark.parametrize("ai_access_setting", ["public", "authenticated", "restricted"])
@@ -433,6 +480,11 @@ def test_models_documents_get_abilities_reader_user(
"favorite": True,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": False,
"partial_update": access_from_link,
@@ -451,7 +503,11 @@ def test_models_documents_get_abilities_reader_user(
document.soft_delete()
document.refresh_from_db()
assert all(value is False for value in document.get_abilities(user).values())
assert all(
value is False
for key, value in document.get_abilities(user).items()
if key != "link_select_options"
)
def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
@@ -478,6 +534,11 @@ def test_models_documents_get_abilities_preset_role(django_assert_num_queries):
"favorite": True,
"invite_owner": False,
"link_configuration": False,
"link_select_options": {
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
"media_auth": True,
"move": False,
"partial_update": False,
@@ -931,3 +992,142 @@ def test_models_documents_restore_complex_bis(django_assert_num_queries):
assert document.ancestors_deleted_at == document.deleted_at
assert child1.ancestors_deleted_at == document.deleted_at
assert child2.ancestors_deleted_at == document.deleted_at
@pytest.mark.parametrize(
"ancestors_links, select_options",
[
# One ancestor
(
[{"link_reach": "public", "link_role": "reader"}],
{
"restricted": ["editor"],
"authenticated": ["editor"],
"public": ["reader", "editor"],
},
),
([{"link_reach": "public", "link_role": "editor"}], {"public": ["editor"]}),
(
[{"link_reach": "authenticated", "link_role": "reader"}],
{
"restricted": ["editor"],
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
},
),
(
[{"link_reach": "authenticated", "link_role": "editor"}],
{"authenticated": ["editor"], "public": ["reader", "editor"]},
),
(
[{"link_reach": "restricted", "link_role": "reader"}],
{
"restricted": ["reader", "editor"],
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
},
),
(
[{"link_reach": "restricted", "link_role": "editor"}],
{
"restricted": ["editor"],
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
},
),
# Multiple ancestors with different roles
(
[
{"link_reach": "public", "link_role": "reader"},
{"link_reach": "public", "link_role": "editor"},
],
{"public": ["editor"]},
),
(
[
{"link_reach": "authenticated", "link_role": "reader"},
{"link_reach": "authenticated", "link_role": "editor"},
],
{"authenticated": ["editor"], "public": ["reader", "editor"]},
),
(
[
{"link_reach": "restricted", "link_role": "reader"},
{"link_reach": "restricted", "link_role": "editor"},
],
{
"restricted": ["editor"],
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
},
),
# Multiple ancestors with different reaches
(
[
{"link_reach": "authenticated", "link_role": "reader"},
{"link_reach": "public", "link_role": "reader"},
],
{
"restricted": ["editor"],
"authenticated": ["editor"],
"public": ["reader", "editor"],
},
),
(
[
{"link_reach": "restricted", "link_role": "reader"},
{"link_reach": "authenticated", "link_role": "reader"},
{"link_reach": "public", "link_role": "reader"},
],
{
"restricted": ["editor"],
"authenticated": ["editor"],
"public": ["reader", "editor"],
},
),
# Multiple ancestors with mixed reaches and roles
(
[
{"link_reach": "authenticated", "link_role": "editor"},
{"link_reach": "public", "link_role": "reader"},
],
{"authenticated": ["editor"], "public": ["reader", "editor"]},
),
(
[
{"link_reach": "authenticated", "link_role": "reader"},
{"link_reach": "public", "link_role": "editor"},
],
{"public": ["editor"]},
),
(
[
{"link_reach": "restricted", "link_role": "editor"},
{"link_reach": "authenticated", "link_role": "reader"},
],
{
"restricted": ["editor"],
"authenticated": ["reader", "editor"],
"public": ["reader", "editor"],
},
),
(
[
{"link_reach": "restricted", "link_role": "reader"},
{"link_reach": "authenticated", "link_role": "editor"},
],
{"authenticated": ["editor"], "public": ["reader", "editor"]},
),
# No ancestors (edge case)
(
[],
{
"public": ["reader", "editor"],
"authenticated": ["reader", "editor"],
"restricted": ["reader", "editor"],
},
),
],
)
def test_models_documents_get_select_options(ancestors_links, select_options):
"""Validate that the "get_select_options" method operates as expected."""
assert models.LinkReachChoices.get_select_options(ancestors_links) == select_options