mirror of
https://github.com/suitenumerique/docs.git
synced 2026-09-25 19:15:14 +02:00
✨(collaboration) show a user the history since they got access
The collaboration server's activity and changeset routes are opened to the browser, so a document's editing history can be read from where it actually lives now. What a user may see of it is bounded to the moment they were given access to the document: joining a document that has been written for a year does not hand them the year. That rule is not new. It is the one the version endpoints have always applied - "only those created after the user got access to the document" - and the date is the same one: the earliest access the user holds on the document or on any of its ancestors, so sharing a folder shares its subtree from that moment. It was computed twice in the backend, differently, and exposed nowhere. It is now a single annotation, user_access_since, that the version endpoints and the collaboration server both read, the latter through the document detail response it already fetches to authorize a connection. The bound is applied server-side and silently: a client asks for whatever range it likes and receives only its own share, so there is no bound for it to get wrong and none it can widen. It is a stored date rather than a wall-clock-relative one, which is what keeps it stable across a websocket re-check, and it is never zero - the one value that would also unlock a full-history connection. A reader who reaches a document through its link alone holds no access and so has no date to bound a history with. They get none, which is why the backend has always refused them their versions. rollback and prune stay refused to everyone: restoring a version is a separate decision. Signed-off-by: Kevin Jahns <kevin.jahns@protonmail.com>
This commit is contained in:
@@ -563,6 +563,7 @@ class DocumentViewSet(
|
||||
all_serializer_class = serializers.ListDocumentSerializer
|
||||
children_serializer_class = serializers.ListDocumentSerializer
|
||||
descendants_serializer_class = serializers.ListDocumentSerializer
|
||||
favorite_list_serializer_class = serializers.ListDocumentSerializer
|
||||
list_serializer_class = serializers.ListDocumentSerializer
|
||||
trashbin_serializer_class = serializers.ListDocumentSerializer
|
||||
tree_serializer_class = serializers.ListDocumentSerializer
|
||||
@@ -612,6 +613,9 @@ class DocumentViewSet(
|
||||
queryset = queryset.annotate_is_favorite(user)
|
||||
queryset = queryset.annotate_user_roles(user)
|
||||
queryset = queryset.annotate_user_has_link_trace(user)
|
||||
# Detail views only — `list` builds its own annotation chain below and does not need
|
||||
# this one, so the list endpoint keeps its current query cost
|
||||
queryset = queryset.annotate_user_access_since(user)
|
||||
|
||||
return queryset
|
||||
|
||||
@@ -1822,16 +1826,14 @@ class DocumentViewSet(
|
||||
document = self.get_object()
|
||||
|
||||
# Users should not see version history dating from before they gained access to the
|
||||
# document. Filter to get the minimum access date for the logged-in user
|
||||
access_queryset = models.DocumentAccess.objects.filter(
|
||||
db.Q(user=user) | db.Q(team__in=user.teams),
|
||||
document__path__in=document.get_self_and_ancestors_paths(),
|
||||
).aggregate(min_date=db.Min("created_at"))
|
||||
|
||||
# Handle the case where the user has no accesses
|
||||
min_datetime = access_queryset["min_date"]
|
||||
if not min_datetime:
|
||||
return drf.exceptions.PermissionDenied(
|
||||
# document. `user_access_since` is annotated onto the queryset (see
|
||||
# `DocumentQuerySet.annotate_user_access_since`) and is the one definition of that
|
||||
# date — the collaboration server is handed the same value to bound the history it
|
||||
# serves. It is None for a user who reaches the document through its link reach
|
||||
# alone: no access, no date, and so no history.
|
||||
min_datetime = document.user_access_since
|
||||
if min_datetime is None:
|
||||
raise drf.exceptions.PermissionDenied(
|
||||
"Only users with specific access can see version history"
|
||||
)
|
||||
|
||||
@@ -1853,22 +1855,21 @@ class DocumentViewSet(
|
||||
"""Custom action to retrieve a specific version of a document"""
|
||||
document = self.get_object()
|
||||
|
||||
# Don't let users access versions that were created before they were given access to
|
||||
# the document — the same cut-off as `versions_list`, from the same annotation.
|
||||
# Checked before the object is fetched: a caller who may see no version at all should
|
||||
# not learn from a 404 whether this one exists.
|
||||
min_datetime = document.user_access_since
|
||||
if min_datetime is None:
|
||||
raise drf.exceptions.PermissionDenied(
|
||||
"Only users with specific access can see version history"
|
||||
)
|
||||
|
||||
try:
|
||||
response = document.get_content_response(version_id=version_id)
|
||||
except (FileNotFoundError, ClientError) as err:
|
||||
raise Http404 from err
|
||||
|
||||
# Don't let users access versions that were created before they were given access
|
||||
# to the document
|
||||
user = request.user
|
||||
min_datetime = min(
|
||||
access.created_at
|
||||
for access in models.DocumentAccess.objects.filter(
|
||||
db.Q(user=user) | db.Q(team__in=user.teams),
|
||||
document__path__in=document.get_self_and_ancestors_paths(),
|
||||
)
|
||||
)
|
||||
|
||||
if response["LastModified"] < min_datetime:
|
||||
raise Http404
|
||||
|
||||
|
||||
Reference in New Issue
Block a user