Compare commits

..

1 Commits

Author SHA1 Message Date
virgile-deville c924706aa8 📝(doc) change documentation folder name
It was called docs which is confusing as it contain the code of the app

Signed-off-by: virgile-deville <virgile.deville@beta.gouv.fr>
2025-04-17 10:44:54 +02:00
22 changed files with 21 additions and 180 deletions
+1 -4
View File
@@ -7,15 +7,12 @@ and this project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
- 📝(doc) change documentation folder name to "documentation" #876
## Added
- 🚩 add homepage feature flag #861
## Fixed
- 🔒️(back) don't allow an owner to change or delete other owner accesses
## [3.1.0] - 2025-04-07
+3 -3
View File
@@ -1,6 +1,6 @@
<p align="center">
<a href="https://github.com/suitenumerique/docs">
<img alt="Docs" src="/docs/assets/docs-logo.png" width="300" />
<img alt="Docs" src="/documentation/assets/docs-logo-black-white.png" width="300" />
</a>
</p>
@@ -20,7 +20,7 @@ Welcome to Docs! The open source document editor where your notes can become kno
</a>
</p>
<img src="/docs/assets/docs_live_collaboration_light.gif" width="100%" align="center"/>
<img src="/documentation/assets/docs_live_collaboration_light.gif" width="100%" align="center"/>
## Why use Docs ❓
@@ -193,5 +193,5 @@ We are proud sponsors of [BlockNotejs](https://www.blocknotejs.org/) and [Yjs](h
We are always looking for new public partners (we are currently onboarding the Netherlands 🇳🇱🧀), feel free to [reach out](mailto:docs@numerique.gouv.fr) if you are interested in using or contributing to Docs.
<p align="center">
<img src="/docs/assets/europe_opensource.png" width="50%"/>
<img src="/documentation/assets/europe_opensource.png" width="50%"/>
</p>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 5.8 MiB

After

Width:  |  Height:  |  Size: 5.8 MiB

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

+1 -7
View File
@@ -1113,17 +1113,11 @@ class DocumentAccess(BaseAccess):
"""
roles = self._get_roles(self.document, user)
is_owner_or_admin = bool(set(roles).intersection(set(PRIVILEGED_ROLES)))
if self.role == RoleChoices.OWNER:
# An owner can only delete its own access if other owners exist
can_delete = (
self.user
and self.user.id == user.id
RoleChoices.OWNER in roles
and self.document.accesses.filter(role=RoleChoices.OWNER).count() > 1
)
# An owner can only update its own access to a non-owner role
# and only if other owners exist
set_role_to = (
[RoleChoices.ADMIN, RoleChoices.EDITOR, RoleChoices.READER]
if can_delete
@@ -1,7 +1,6 @@
"""
Test document accesses API endpoints for users in impress's core app.
"""
# pylint: disable=too-many-lines
import random
from uuid import uuid4
@@ -625,18 +624,14 @@ def test_api_document_accesses_update_administrator_to_owner(
@pytest.mark.parametrize("via", VIA)
@pytest.mark.parametrize(
"role", [role for role in models.RoleChoices if role != models.RoleChoices.OWNER]
)
def test_api_document_accesses_update_owner(
via,
role,
mock_user_teams,
mock_reset_connections, # pylint: disable=redefined-outer-name
):
"""
A user who is an owner in a document should be allowed to update
a user access for this document for all roles except owner.
a user access for this document whatever the role.
"""
user = factories.UserFactory(with_owned_document=True)
@@ -653,7 +648,9 @@ def test_api_document_accesses_update_owner(
)
factories.UserFactory()
access = factories.UserDocumentAccessFactory(document=document, role=role)
access = factories.UserDocumentAccessFactory(
document=document,
)
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_values = {
@@ -732,30 +729,11 @@ def test_api_document_accesses_update_owner_self(
access.refresh_from_db()
assert access.role == "owner"
# Add another owner and it should now work only for user, not for team
# Add another owner and it should now work
factories.UserDocumentAccessFactory(document=document, role="owner")
if via == USER:
factories.UserDocumentAccessFactory(document=document, role="owner")
user_id = str(access.user_id) if via == USER else None
with mock_reset_connections(document.id, user_id):
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data={
**old_values,
"role": new_role,
"user_id": old_values.get("user", {}).get("id")
if old_values.get("user") is not None
else None,
},
format="json",
)
assert response.status_code == 200
access.refresh_from_db()
assert access.role == new_role
else:
user_id = str(access.user_id) if via == USER else None
with mock_reset_connections(document.id, user_id):
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data={
@@ -767,41 +745,13 @@ def test_api_document_accesses_update_owner_self(
},
format="json",
)
assert response.status_code == 403
assert response.status_code == 200
access.refresh_from_db()
assert access.role == new_role
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_update_owner_from_other_owner(via, mock_user_teams):
"""
A user who is an owner in a document should not be allowed to update
access for another user who is also an owner in the document.
"""
user = factories.UserFactory(with_owned_document=True)
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
elif via == TEAM:
mock_user_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="owner"
)
other_owner = factories.UserFactory()
access = factories.UserDocumentAccessFactory(
document=document, user=other_owner, role="owner"
)
old_values = serializers.DocumentAccessSerializer(instance=access).data
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data={**old_values, "role": models.RoleChoices.ADMIN},
format="json",
)
assert response.status_code == 403
# Delete
def test_api_document_accesses_delete_anonymous():
@@ -948,18 +898,14 @@ def test_api_document_accesses_delete_administrator_on_owners(via, mock_user_tea
@pytest.mark.parametrize("via", VIA)
@pytest.mark.parametrize(
"role", [role for role in models.RoleChoices if role != models.RoleChoices.OWNER]
)
def test_api_document_accesses_delete_owners(
via,
role,
mock_user_teams,
mock_reset_connections, # pylint: disable=redefined-outer-name
):
"""
Users should be able to delete the document access of another user
for a document of which they are owner but can not delete other owners.
for a document of which they are owner.
"""
user = factories.UserFactory()
@@ -975,7 +921,7 @@ def test_api_document_accesses_delete_owners(
document=document, team="lasuite", role="owner"
)
access = factories.UserDocumentAccessFactory(document=document, role=role)
access = factories.UserDocumentAccessFactory(document=document)
assert models.DocumentAccess.objects.count() == 2
assert models.DocumentAccess.objects.filter(user=access.user).exists()
@@ -989,79 +935,6 @@ def test_api_document_accesses_delete_owners(
assert models.DocumentAccess.objects.count() == 1
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_delete_other_owners(via, mock_user_teams):
"""
A user should not be able to delete the document access of another owner.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
elif via == TEAM:
mock_user_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="owner"
)
access = factories.UserDocumentAccessFactory(document=document, role="owner")
assert models.DocumentAccess.objects.count() == 2
assert models.DocumentAccess.objects.filter(user=access.user).exists()
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 403
assert models.DocumentAccess.objects.count() == 2
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_delete_self_owner_if_other_owner_exists(
via,
mock_reset_connections, # pylint: disable=redefined-outer-name
):
"""
A user should be able to delete their own ownership access if there is another owner in the
document.
"""
user = factories.UserFactory(with_owned_document=True)
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
access = None
if via == USER:
access = factories.UserDocumentAccessFactory(
document=document, user=user, role="owner"
)
elif via == TEAM:
pytest.skip("Implement when team ownership is implemented")
factories.UserDocumentAccessFactory(document=document, role="owner")
assert (
models.DocumentAccess.objects.filter(document=document, role="owner").count()
== 2
)
with mock_reset_connections(document.id, str(access.user_id)):
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 204
assert (
models.DocumentAccess.objects.filter(document=document, role="owner").count()
== 1
)
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_delete_owners_last_owner(via, mock_user_teams):
"""
@@ -1084,16 +957,10 @@ def test_api_document_accesses_delete_owners_last_owner(via, mock_user_teams):
document=document, team="lasuite", role="owner"
)
assert (
models.DocumentAccess.objects.filter(document=document, role="owner").count()
== 1
)
assert models.DocumentAccess.objects.count() == 2
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 403
assert (
models.DocumentAccess.objects.filter(document=document, role="owner").count()
== 1
)
assert models.DocumentAccess.objects.count() == 2
@@ -150,23 +150,6 @@ def test_models_document_access_get_abilities_for_owner_of_owner():
document=access.document, role="owner"
).user
abilities = access.get_abilities(user)
assert abilities == {
"destroy": False,
"retrieve": True,
"update": False,
"partial_update": False,
"set_role_to": [],
}
def test_models_document_access_get_abilities_for_owner_of_self_with_other_owner():
"""
Check abilities of self access for the owner of a document when there is at least one other
owner left.
"""
access = factories.UserDocumentAccessFactory(role="owner")
factories.UserDocumentAccessFactory(document=access.document, role="owner")
abilities = access.get_abilities(access.user)
assert abilities == {
"destroy": True,
"retrieve": True,