Skip to content

Commit 364189b

Browse files
committed
🐛(backend) fix creating/updating document accesses for teams
This use case was forgotten when the support for team accesses was added. We add tests to stabilize the feature and its security.
1 parent 3057043 commit 364189b

File tree

3 files changed

+170
-12
lines changed

3 files changed

+170
-12
lines changed

src/backend/core/api/viewsets.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -1475,14 +1475,15 @@ def perform_create(self, serializer):
14751475

14761476
access = serializer.save(document_id=self.kwargs["resource_id"])
14771477

1478-
access.document.send_invitation_email(
1479-
access.user.email,
1480-
access.role,
1481-
self.request.user,
1482-
access.user.language
1483-
or self.request.user.language
1484-
or settings.LANGUAGE_CODE,
1485-
)
1478+
if access.user:
1479+
access.document.send_invitation_email(
1480+
access.user.email,
1481+
access.role,
1482+
self.request.user,
1483+
access.user.language
1484+
or self.request.user.language
1485+
or settings.LANGUAGE_CODE,
1486+
)
14861487

14871488
def perform_update(self, serializer):
14881489
"""Update an access to the document and notify the collaboration server."""

src/backend/core/tests/documents/test_api_document_accesses.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,9 @@ def test_api_document_accesses_update_authenticated_reader_or_editor(
654654

655655

656656
@pytest.mark.parametrize("via", VIA)
657+
@pytest.mark.parametrize("create_for", VIA)
657658
def test_api_document_accesses_update_administrator_except_owner(
659+
create_for,
658660
via,
659661
mock_user_teams,
660662
mock_reset_connections, # pylint: disable=redefined-outer-name
@@ -687,9 +689,12 @@ def test_api_document_accesses_update_administrator_except_owner(
687689

688690
new_values = {
689691
"id": uuid4(),
690-
"user_id": factories.UserFactory().id,
691692
"role": random.choice(["administrator", "editor", "reader"]),
692693
}
694+
if create_for == USER:
695+
new_values["user_id"] = factories.UserFactory().id
696+
elif create_for == TEAM:
697+
new_values["team"] = "new-team"
693698

694699
for field, value in new_values.items():
695700
new_data = {**old_values, field: value}
@@ -825,7 +830,9 @@ def test_api_document_accesses_update_administrator_to_owner(
825830

826831

827832
@pytest.mark.parametrize("via", VIA)
833+
@pytest.mark.parametrize("create_for", VIA)
828834
def test_api_document_accesses_update_owner(
835+
create_for,
829836
via,
830837
mock_user_teams,
831838
mock_reset_connections, # pylint: disable=redefined-outer-name
@@ -856,9 +863,12 @@ def test_api_document_accesses_update_owner(
856863

857864
new_values = {
858865
"id": uuid4(),
859-
"user_id": factories.UserFactory().id,
860866
"role": random.choice(models.RoleChoices.values),
861867
}
868+
if create_for == USER:
869+
new_values["user_id"] = factories.UserFactory().id
870+
elif create_for == TEAM:
871+
new_values["team"] = "new-team"
862872

863873
for field, value in new_values.items():
864874
new_data = {**old_values, field: value}

src/backend/core/tests/documents/test_api_document_accesses_create.py

+149-2
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ def test_api_document_accesses_create_authenticated_reader_or_editor(
105105

106106
@pytest.mark.parametrize("depth", [1, 2, 3])
107107
@pytest.mark.parametrize("via", VIA)
108-
def test_api_document_accesses_create_authenticated_administrator(
108+
def test_api_document_accesses_create_authenticated_administrator_share_to_user(
109109
via, depth, mock_user_teams
110110
):
111111
"""
@@ -195,7 +195,90 @@ def test_api_document_accesses_create_authenticated_administrator(
195195

196196
@pytest.mark.parametrize("depth", [1, 2, 3])
197197
@pytest.mark.parametrize("via", VIA)
198-
def test_api_document_accesses_create_authenticated_owner(via, depth, mock_user_teams):
198+
def test_api_document_accesses_create_authenticated_administrator_share_to_team(
199+
via, depth, mock_user_teams
200+
):
201+
"""
202+
Administrators of a document (direct or by heritage) should be able to create
203+
document accesses except for the "owner" role.
204+
An email should be sent to the accesses to notify them of the adding.
205+
"""
206+
user = factories.UserFactory(with_owned_document=True)
207+
client = APIClient()
208+
client.force_login(user)
209+
210+
documents = []
211+
for i in range(depth):
212+
parent = documents[i - 1] if i > 0 else None
213+
documents.append(factories.DocumentFactory(parent=parent))
214+
215+
if via == USER:
216+
factories.UserDocumentAccessFactory(
217+
document=documents[0], user=user, role="administrator"
218+
)
219+
elif via == TEAM:
220+
mock_user_teams.return_value = ["lasuite", "unknown"]
221+
factories.TeamDocumentAccessFactory(
222+
document=documents[0], team="lasuite", role="administrator"
223+
)
224+
225+
other_user = factories.UserFactory(language="en-us")
226+
document = documents[-1]
227+
response = client.post(
228+
f"/api/v1.0/documents/{document.id!s}/accesses/",
229+
{
230+
"team": "new-team",
231+
"role": "owner",
232+
},
233+
format="json",
234+
)
235+
236+
assert response.status_code == 403
237+
assert response.json() == {
238+
"detail": "Only owners of a document can assign other users as owners."
239+
}
240+
241+
# It should be allowed to create a lower access
242+
role = random.choice(
243+
[role[0] for role in models.RoleChoices.choices if role[0] != "owner"]
244+
)
245+
246+
assert len(mail.outbox) == 0
247+
248+
response = client.post(
249+
f"/api/v1.0/documents/{document.id!s}/accesses/",
250+
{
251+
"team": "new-team",
252+
"role": role,
253+
},
254+
format="json",
255+
)
256+
257+
assert response.status_code == 201
258+
assert models.DocumentAccess.objects.filter(team="new-team").count() == 1
259+
new_document_access = models.DocumentAccess.objects.filter(team="new-team").get()
260+
other_user = serializers.UserSerializer(instance=other_user).data
261+
assert response.json() == {
262+
"abilities": new_document_access.get_abilities(user),
263+
"document": {
264+
"id": str(new_document_access.document_id),
265+
"depth": new_document_access.document.depth,
266+
"path": new_document_access.document.path,
267+
},
268+
"id": str(new_document_access.id),
269+
"user": None,
270+
"team": "new-team",
271+
"role": role,
272+
"max_ancestors_role": None,
273+
}
274+
assert len(mail.outbox) == 0
275+
276+
277+
@pytest.mark.parametrize("depth", [1, 2, 3])
278+
@pytest.mark.parametrize("via", VIA)
279+
def test_api_document_accesses_create_authenticated_owner_share_to_user(
280+
via, depth, mock_user_teams
281+
):
199282
"""
200283
Owners of a document (direct or by heritage) should be able to create document accesses
201284
whatever the role. An email should be sent to the accesses to notify them of the adding.
@@ -264,6 +347,70 @@ def test_api_document_accesses_create_authenticated_owner(via, depth, mock_user_
264347
assert "docs/" + str(document.id) + "/" in email_content
265348

266349

350+
@pytest.mark.parametrize("depth", [1, 2, 3])
351+
@pytest.mark.parametrize("via", VIA)
352+
def test_api_document_accesses_create_authenticated_owner_share_to_team(
353+
via, depth, mock_user_teams
354+
):
355+
"""
356+
Owners of a document (direct or by heritage) should be able to create document accesses
357+
whatever the role. An email should be sent to the accesses to notify them of the adding.
358+
"""
359+
user = factories.UserFactory()
360+
361+
client = APIClient()
362+
client.force_login(user)
363+
364+
documents = []
365+
for i in range(depth):
366+
parent = documents[i - 1] if i > 0 else None
367+
documents.append(factories.DocumentFactory(parent=parent))
368+
369+
if via == USER:
370+
factories.UserDocumentAccessFactory(
371+
document=documents[0], user=user, role="owner"
372+
)
373+
elif via == TEAM:
374+
mock_user_teams.return_value = ["lasuite", "unknown"]
375+
factories.TeamDocumentAccessFactory(
376+
document=documents[0], team="lasuite", role="owner"
377+
)
378+
379+
other_user = factories.UserFactory(language="en-us")
380+
document = documents[-1]
381+
role = random.choice([role[0] for role in models.RoleChoices.choices])
382+
383+
assert len(mail.outbox) == 0
384+
385+
response = client.post(
386+
f"/api/v1.0/documents/{document.id!s}/accesses/",
387+
{
388+
"team": "new-team",
389+
"role": role,
390+
},
391+
format="json",
392+
)
393+
394+
assert response.status_code == 201
395+
assert models.DocumentAccess.objects.filter(team="new-team").count() == 1
396+
new_document_access = models.DocumentAccess.objects.filter(team="new-team").get()
397+
other_user = serializers.UserSerializer(instance=other_user).data
398+
assert response.json() == {
399+
"document": {
400+
"id": str(new_document_access.document_id),
401+
"path": new_document_access.document.path,
402+
"depth": new_document_access.document.depth,
403+
},
404+
"id": str(new_document_access.id),
405+
"user": None,
406+
"team": "new-team",
407+
"role": role,
408+
"max_ancestors_role": None,
409+
"abilities": new_document_access.get_abilities(user),
410+
}
411+
assert len(mail.outbox) == 0
412+
413+
267414
@pytest.mark.parametrize("via", VIA)
268415
def test_api_document_accesses_create_email_in_receivers_language(via, mock_user_teams):
269416
"""

0 commit comments

Comments
 (0)