From 6e1b65d6e55db748798db88a5efbccc98979a49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antti=20M=C3=A4ki?= Date: Tue, 2 Jan 2024 15:22:17 +0200 Subject: [PATCH] Cyberstorm API: censor deactived package dependencies The reason why a package has been deactivated may be an obnoxious icon or description text, so don't send those to clients. Method serializers are used instead of annotations, since especially for the icon field the annotation isn't straightforward: the ImageField can't be directly attached to the object, and parsing the absolute URL correctly from the path representation stored in the database is error-prone. Refs TS-2013 --- .../cyberstorm/tests/test_package_listing.py | 20 ++++++++++++++++ .../api/cyberstorm/views/package_listing.py | 24 +++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/django/thunderstore/api/cyberstorm/tests/test_package_listing.py b/django/thunderstore/api/cyberstorm/tests/test_package_listing.py index 3047f62a8..6918795a5 100644 --- a/django/thunderstore/api/cyberstorm/tests/test_package_listing.py +++ b/django/thunderstore/api/cyberstorm/tests/test_package_listing.py @@ -312,5 +312,25 @@ def test_dependency_serializer__reads_is_active_from_correct_field( assert actual["is_active"] == (package_is_active and version_is_active) +@pytest.mark.django_db +def test_dependency_serializer__when_dependency_is_not_active__censors_icon_and_description() -> None: + # community_identifier is normally added using annotations, but + # it's irrelavant for this test case. + dependency = PackageVersionFactory() + dependency.community_identifier = "greendale" + + actual = DependencySerializer(dependency).data + + assert actual["description"].startswith("Desc_") + assert actual["icon_url"].startswith("http") + + dependency.is_active = False + del dependency.is_effectively_active # Clear cached property + actual = DependencySerializer(dependency).data + + assert actual["description"] == "This package has been removed." + assert actual["icon_url"] is None + + def _date_to_z(value: datetime) -> str: return value.strftime("%Y-%m-%dT%H:%M:%S.%fZ") diff --git a/django/thunderstore/api/cyberstorm/views/package_listing.py b/django/thunderstore/api/cyberstorm/views/package_listing.py index 56a4550af..5a8722cde 100644 --- a/django/thunderstore/api/cyberstorm/views/package_listing.py +++ b/django/thunderstore/api/cyberstorm/views/package_listing.py @@ -1,3 +1,5 @@ +from typing import Optional + from django.db.models import ( BooleanField, CharField, @@ -27,18 +29,32 @@ class DependencySerializer(serializers.Serializer): """ Dependencies of a given PackageVersion, listed in a given Community. - community_identifier and namespace are not present by default and - need to be annotated to the object. + community_identifier is not present by default and needs to be + annotated to the object. + + Description and icon is not shown to clients if the dependency is + deactivated, since the fields may contain the very reason for the + deactivation. """ community_identifier = serializers.CharField() - description = serializers.CharField() - icon_url = serializers.CharField(source="icon.url") + description = serializers.SerializerMethodField() + icon_url = serializers.SerializerMethodField() is_active = serializers.BooleanField(source="is_effectively_active") name = serializers.CharField() namespace = serializers.CharField(source="package.namespace.name") version_number = serializers.CharField() + def get_description(self, obj: PackageVersion) -> str: + return ( + obj.description + if obj.is_effectively_active + else "This package has been removed." + ) + + def get_icon_url(self, obj: PackageVersion) -> Optional[str]: + return obj.icon.url if obj.is_effectively_active else None + class TeamSerializer(serializers.Serializer): """