diff --git a/api/funkwhale_api/federation/tasks.py b/api/funkwhale_api/federation/tasks.py index 67f8fabc9..d7c48957a 100644 --- a/api/funkwhale_api/federation/tasks.py +++ b/api/funkwhale_api/federation/tasks.py @@ -198,27 +198,34 @@ def delete_qs(qs): ) -def handle_purge_actors(ids): +def handle_purge_actors(ids, only=[]): + """ + Empty only means we purge everything + Otherwise, we purge only the requested bits: media + """ # purge follows (received emitted) - delete_qs(models.LibraryFollow.objects.filter(target__actor_id__in=ids)) - delete_qs(models.LibraryFollow.objects.filter(actor_id__in=ids)) - delete_qs(models.Follow.objects.filter(target_id__in=ids)) - delete_qs(models.Follow.objects.filter(actor_id__in=ids)) + if not only: + delete_qs(models.LibraryFollow.objects.filter(target__actor_id__in=ids)) + delete_qs(models.Follow.objects.filter(actor_id__in=ids)) # purge audio content - delete_qs(music_models.Upload.objects.filter(library__actor_id__in=ids)) - delete_qs(music_models.Library.objects.filter(actor_id__in=ids)) + if not only or "media" in only: + delete_qs(models.LibraryFollow.objects.filter(actor_id__in=ids)) + delete_qs(models.Follow.objects.filter(target_id__in=ids)) + delete_qs(music_models.Upload.objects.filter(library__actor_id__in=ids)) + delete_qs(music_models.Library.objects.filter(actor_id__in=ids)) # purge remaining activities / deliveries - delete_qs(models.InboxItem.objects.filter(actor_id__in=ids)) - delete_qs(models.Activity.objects.filter(actor_id__in=ids)) + if not only: + delete_qs(models.InboxItem.objects.filter(actor_id__in=ids)) + delete_qs(models.Activity.objects.filter(actor_id__in=ids)) @celery.app.task(name="federation.purge_actors") -def purge_actors(ids=[], domains=[]): +def purge_actors(ids=[], domains=[], only=[]): actors = models.Actor.objects.filter( Q(id__in=ids) | Q(domain_id__in=domains) ).order_by("id") found_ids = list(actors.values_list("id", flat=True)) logger.info("Starting purging %s accounts", len(found_ids)) - handle_purge_actors(ids=found_ids) + handle_purge_actors(ids=found_ids, only=only) diff --git a/api/funkwhale_api/manage/serializers.py b/api/funkwhale_api/manage/serializers.py index e8d1a294f..ed50d8677 100644 --- a/api/funkwhale_api/manage/serializers.py +++ b/api/funkwhale_api/manage/serializers.py @@ -317,17 +317,25 @@ class ManageInstancePolicySerializer(serializers.ModelSerializer): @transaction.atomic def save(self, *args, **kwargs): instance = super().save(*args, **kwargs) - need_purge = self.instance.is_active and self.instance.block_all - + need_purge = self.instance.is_active and ( + self.instance.block_all or self.instance.reject_media + ) if need_purge: + only = [] + if self.instance.reject_media: + only.append("media") target = instance.target if target["type"] == "domain": common_utils.on_commit( - federation_tasks.purge_actors.delay, domains=[target["obj"].pk] + federation_tasks.purge_actors.delay, + domains=[target["obj"].pk], + only=only, ) if target["type"] == "actor": common_utils.on_commit( - federation_tasks.purge_actors.delay, ids=[target["obj"].pk] + federation_tasks.purge_actors.delay, + ids=[target["obj"].pk], + only=only, ) return instance diff --git a/api/tests/federation/test_tasks.py b/api/tests/federation/test_tasks.py index e53981069..5e10dfa50 100644 --- a/api/tests/federation/test_tasks.py +++ b/api/tests/federation/test_tasks.py @@ -223,11 +223,46 @@ def test_handle_purge_actors(factories, mocker): d.refresh_from_db() +def test_handle_purge_actors_restrict_media(factories, mocker): + to_purge = factories["federation.Actor"]() + keeped = [ + factories["music.Upload"](), + factories["federation.Activity"](), + factories["federation.InboxItem"](), + factories["federation.Follow"](), + factories["federation.LibraryFollow"](), + factories["federation.Activity"](actor=to_purge), + factories["federation.InboxItem"](actor=to_purge), + factories["federation.Follow"](actor=to_purge), + ] + + library = factories["music.Library"](actor=to_purge) + deleted = [ + library, + factories["music.Upload"](library=library), + factories["federation.LibraryFollow"](actor=to_purge), + ] + + tasks.handle_purge_actors([to_purge.pk], only=["media"]) + + for k in keeped: + # this should not be deleted + k.refresh_from_db() + + for d in deleted: + with pytest.raises(d.__class__.DoesNotExist): + d.refresh_from_db() + + def test_purge_actors(factories, mocker): handle_purge_actors = mocker.spy(tasks, "handle_purge_actors") factories["federation.Actor"]() to_delete = factories["federation.Actor"]() to_delete_domain = factories["federation.Actor"]() - tasks.purge_actors(ids=[to_delete.pk], domains=[to_delete_domain.domain.name]) + tasks.purge_actors( + ids=[to_delete.pk], domains=[to_delete_domain.domain.name], only=["hello"] + ) - handle_purge_actors.assert_called_once_with(ids=[to_delete.pk, to_delete_domain.pk]) + handle_purge_actors.assert_called_once_with( + ids=[to_delete.pk, to_delete_domain.pk], only=["hello"] + ) diff --git a/api/tests/manage/test_serializers.py b/api/tests/manage/test_serializers.py index 1cb3ff902..53bc2504b 100644 --- a/api/tests/manage/test_serializers.py +++ b/api/tests/manage/test_serializers.py @@ -175,65 +175,85 @@ def test_manage_domain_action_purge(factories, mocker): ) -def test_instance_policy_serializer_purges_target_domain(factories, mocker): - policy = factories["moderation.InstancePolicy"](for_domain=True, block_all=False) +@pytest.mark.parametrize( + "param,expected_only", [("block_all", []), ("reject_media", ["media"])] +) +def test_instance_policy_serializer_purges_target_domain( + factories, mocker, param, expected_only +): + params = {param: False} + if param != "block_all": + params["block_all"] = False + policy = factories["moderation.InstancePolicy"](for_domain=True, **params) on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") serializer = serializers.ManageInstancePolicySerializer( - policy, data={"block_all": True}, partial=True + policy, data={param: True}, partial=True ) serializer.is_valid(raise_exception=True) serializer.save() policy.refresh_from_db() - assert policy.block_all is True + assert getattr(policy, param) is True on_commit.assert_called_once_with( - federation_tasks.purge_actors.delay, domains=[policy.target_domain_id] + federation_tasks.purge_actors.delay, + domains=[policy.target_domain_id], + only=expected_only, ) on_commit.reset_mock() # setting to false should have no effect serializer = serializers.ManageInstancePolicySerializer( - policy, data={"block_all": False}, partial=True + policy, data={param: False}, partial=True ) serializer.is_valid(raise_exception=True) serializer.save() policy.refresh_from_db() - assert policy.block_all is False + assert getattr(policy, param) is False assert on_commit.call_count == 0 -def test_instance_policy_serializer_purges_target_actor(factories, mocker): - policy = factories["moderation.InstancePolicy"](for_actor=True, block_all=False) +@pytest.mark.parametrize( + "param,expected_only", [("block_all", []), ("reject_media", ["media"])] +) +def test_instance_policy_serializer_purges_target_actor( + factories, mocker, param, expected_only +): + params = {param: False} + if param != "block_all": + params["block_all"] = False + policy = factories["moderation.InstancePolicy"](for_actor=True, **params) on_commit = mocker.patch("funkwhale_api.common.utils.on_commit") serializer = serializers.ManageInstancePolicySerializer( - policy, data={"block_all": True}, partial=True + policy, data={param: True}, partial=True ) serializer.is_valid(raise_exception=True) serializer.save() policy.refresh_from_db() - assert policy.block_all is True + assert getattr(policy, param) is True on_commit.assert_called_once_with( - federation_tasks.purge_actors.delay, ids=[policy.target_actor_id] + federation_tasks.purge_actors.delay, + ids=[policy.target_actor_id], + only=expected_only, ) on_commit.reset_mock() # setting to false should have no effect serializer = serializers.ManageInstancePolicySerializer( - policy, data={"block_all": False}, partial=True + policy, data={param: False}, partial=True ) serializer.is_valid(raise_exception=True) serializer.save() policy.refresh_from_db() - assert policy.block_all is False + assert getattr(policy, param) is False assert on_commit.call_count == 0 diff --git a/front/src/components/manage/moderation/InstancePolicyForm.vue b/front/src/components/manage/moderation/InstancePolicyForm.vue index 52084bd5a..98fd1d644 100644 --- a/front/src/components/manage/moderation/InstancePolicyForm.vue +++ b/front/src/components/manage/moderation/InstancePolicyForm.vue @@ -119,7 +119,7 @@ export default { label: this.$gettext("Silence notifications"), }, rejectMedia: { - help: this.$gettext("Do not download any media file (audio, album cover, account avatar…) from this account or domain."), + help: this.$gettext("Do not download any media file (audio, album cover, account avatar…) from this account or domain. This will purge existing content as well."), label: this.$gettext("Reject media"), } }