kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
Fix #858: Broadcast/handle rejected follows
rodzic
191f28f79c
commit
cd109ddeb6
|
@ -34,6 +34,8 @@ def update_follow(follow, approved):
|
||||||
follow.save(update_fields=["approved"])
|
follow.save(update_fields=["approved"])
|
||||||
if approved:
|
if approved:
|
||||||
routes.outbox.dispatch({"type": "Accept"}, context={"follow": follow})
|
routes.outbox.dispatch({"type": "Accept"}, context={"follow": follow})
|
||||||
|
else:
|
||||||
|
routes.outbox.dispatch({"type": "Reject"}, context={"follow": follow})
|
||||||
|
|
||||||
|
|
||||||
class LibraryFollowViewSet(
|
class LibraryFollowViewSet(
|
||||||
|
@ -57,7 +59,7 @@ class LibraryFollowViewSet(
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
qs = super().get_queryset()
|
qs = super().get_queryset()
|
||||||
return qs.filter(actor=self.request.user.actor)
|
return qs.filter(actor=self.request.user.actor).exclude(approved=False)
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
follow = serializer.save(actor=self.request.user.actor)
|
follow = serializer.save(actor=self.request.user.actor)
|
||||||
|
|
|
@ -82,6 +82,37 @@ def outbox_accept(context):
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@outbox.register({"type": "Reject"})
|
||||||
|
def outbox_reject_follow(context):
|
||||||
|
follow = context["follow"]
|
||||||
|
if follow._meta.label == "federation.LibraryFollow":
|
||||||
|
actor = follow.target.actor
|
||||||
|
else:
|
||||||
|
actor = follow.target
|
||||||
|
payload = serializers.RejectFollowSerializer(follow, context={"actor": actor}).data
|
||||||
|
yield {
|
||||||
|
"actor": actor,
|
||||||
|
"type": "Reject",
|
||||||
|
"payload": with_recipients(payload, to=[follow.actor]),
|
||||||
|
"object": follow,
|
||||||
|
"related_object": follow.target,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@inbox.register({"type": "Reject"})
|
||||||
|
def inbox_reject_follow(payload, context):
|
||||||
|
serializer = serializers.RejectFollowSerializer(data=payload, context=context)
|
||||||
|
if not serializer.is_valid(raise_exception=context.get("raise_exception", False)):
|
||||||
|
logger.debug(
|
||||||
|
"Discarding invalid follow reject from %s: %s",
|
||||||
|
context["actor"].fid,
|
||||||
|
serializer.errors,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
serializer.save()
|
||||||
|
|
||||||
|
|
||||||
@inbox.register({"type": "Undo", "object.type": "Follow"})
|
@inbox.register({"type": "Undo", "object.type": "Follow"})
|
||||||
def inbox_undo_follow(payload, context):
|
def inbox_undo_follow(payload, context):
|
||||||
serializer = serializers.UndoFollowSerializer(data=payload, context=context)
|
serializer = serializers.UndoFollowSerializer(data=payload, context=context)
|
||||||
|
|
|
@ -688,11 +688,10 @@ class APIFollowSerializer(serializers.ModelSerializer):
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class AcceptFollowSerializer(serializers.Serializer):
|
class FollowActionSerializer(serializers.Serializer):
|
||||||
id = serializers.URLField(max_length=500, required=False)
|
id = serializers.URLField(max_length=500, required=False)
|
||||||
actor = serializers.URLField(max_length=500)
|
actor = serializers.URLField(max_length=500)
|
||||||
object = FollowSerializer()
|
object = FollowSerializer()
|
||||||
type = serializers.ChoiceField(choices=["Accept"])
|
|
||||||
|
|
||||||
def validate_actor(self, v):
|
def validate_actor(self, v):
|
||||||
expected = self.context.get("actor")
|
expected = self.context.get("actor")
|
||||||
|
@ -720,12 +719,13 @@ class AcceptFollowSerializer(serializers.Serializer):
|
||||||
follow_class.objects.filter(
|
follow_class.objects.filter(
|
||||||
target=target, actor=validated_data["object"]["actor"]
|
target=target, actor=validated_data["object"]["actor"]
|
||||||
)
|
)
|
||||||
.exclude(approved=True)
|
|
||||||
.select_related()
|
.select_related()
|
||||||
.get()
|
.get()
|
||||||
)
|
)
|
||||||
except follow_class.DoesNotExist:
|
except follow_class.DoesNotExist:
|
||||||
raise serializers.ValidationError("No follow to accept")
|
raise serializers.ValidationError(
|
||||||
|
"No follow to {}".format(self.action_type)
|
||||||
|
)
|
||||||
return validated_data
|
return validated_data
|
||||||
|
|
||||||
def to_representation(self, instance):
|
def to_representation(self, instance):
|
||||||
|
@ -736,12 +736,18 @@ class AcceptFollowSerializer(serializers.Serializer):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"@context": jsonld.get_default_context(),
|
"@context": jsonld.get_default_context(),
|
||||||
"id": instance.get_federation_id() + "/accept",
|
"id": instance.get_federation_id() + "/{}".format(self.action_type),
|
||||||
"type": "Accept",
|
"type": self.action_type.title(),
|
||||||
"actor": actor.fid,
|
"actor": actor.fid,
|
||||||
"object": FollowSerializer(instance).data,
|
"object": FollowSerializer(instance).data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class AcceptFollowSerializer(FollowActionSerializer):
|
||||||
|
|
||||||
|
type = serializers.ChoiceField(choices=["Accept"])
|
||||||
|
action_type = "accept"
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
follow = self.validated_data["follow"]
|
follow = self.validated_data["follow"]
|
||||||
follow.approved = True
|
follow.approved = True
|
||||||
|
@ -751,6 +757,18 @@ class AcceptFollowSerializer(serializers.Serializer):
|
||||||
return follow
|
return follow
|
||||||
|
|
||||||
|
|
||||||
|
class RejectFollowSerializer(FollowActionSerializer):
|
||||||
|
|
||||||
|
type = serializers.ChoiceField(choices=["Reject"])
|
||||||
|
action_type = "reject"
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
follow = self.validated_data["follow"]
|
||||||
|
follow.approved = False
|
||||||
|
follow.save()
|
||||||
|
return follow
|
||||||
|
|
||||||
|
|
||||||
class UndoFollowSerializer(serializers.Serializer):
|
class UndoFollowSerializer(serializers.Serializer):
|
||||||
id = serializers.URLField(max_length=500)
|
id = serializers.URLField(max_length=500)
|
||||||
actor = serializers.URLField(max_length=500)
|
actor = serializers.URLField(max_length=500)
|
||||||
|
|
|
@ -126,12 +126,9 @@ def test_user_can_accept_or_reject_own_follows(
|
||||||
|
|
||||||
assert follow.approved is expected
|
assert follow.approved is expected
|
||||||
|
|
||||||
if action == "accept":
|
mocked_dispatch.assert_called_once_with(
|
||||||
mocked_dispatch.assert_called_once_with(
|
{"type": action.title()}, context={"follow": follow}
|
||||||
{"type": "Accept"}, context={"follow": follow}
|
)
|
||||||
)
|
|
||||||
if action == "reject":
|
|
||||||
mocked_dispatch.assert_not_called()
|
|
||||||
|
|
||||||
|
|
||||||
def test_user_can_list_inbox_items(factories, logged_in_api_client):
|
def test_user_can_list_inbox_items(factories, logged_in_api_client):
|
||||||
|
|
|
@ -16,6 +16,7 @@ from funkwhale_api.moderation import serializers as moderation_serializers
|
||||||
[
|
[
|
||||||
({"type": "Follow"}, routes.inbox_follow),
|
({"type": "Follow"}, routes.inbox_follow),
|
||||||
({"type": "Accept"}, routes.inbox_accept),
|
({"type": "Accept"}, routes.inbox_accept),
|
||||||
|
({"type": "Reject"}, routes.inbox_reject_follow),
|
||||||
({"type": "Create", "object": {"type": "Audio"}}, routes.inbox_create_audio),
|
({"type": "Create", "object": {"type": "Audio"}}, routes.inbox_create_audio),
|
||||||
(
|
(
|
||||||
{"type": "Update", "object": {"type": "Library"}},
|
{"type": "Update", "object": {"type": "Library"}},
|
||||||
|
@ -51,6 +52,7 @@ def test_inbox_routes(route, handler):
|
||||||
({"type": "Accept"}, routes.outbox_accept),
|
({"type": "Accept"}, routes.outbox_accept),
|
||||||
({"type": "Flag"}, routes.outbox_flag),
|
({"type": "Flag"}, routes.outbox_flag),
|
||||||
({"type": "Follow"}, routes.outbox_follow),
|
({"type": "Follow"}, routes.outbox_follow),
|
||||||
|
({"type": "Reject"}, routes.outbox_reject_follow),
|
||||||
({"type": "Create", "object": {"type": "Audio"}}, routes.outbox_create_audio),
|
({"type": "Create", "object": {"type": "Audio"}}, routes.outbox_create_audio),
|
||||||
(
|
(
|
||||||
{"type": "Update", "object": {"type": "Library"}},
|
{"type": "Update", "object": {"type": "Library"}},
|
||||||
|
@ -669,6 +671,46 @@ def test_outbox_delete_follow_library(factories):
|
||||||
assert activity["related_object"] == follow.target
|
assert activity["related_object"] == follow.target
|
||||||
|
|
||||||
|
|
||||||
|
def test_inbox_reject_follow_library(factories):
|
||||||
|
local_actor = factories["users.User"]().create_actor()
|
||||||
|
remote_actor = factories["federation.Actor"]()
|
||||||
|
follow = factories["federation.LibraryFollow"](
|
||||||
|
actor=local_actor, target__actor=remote_actor, approved=True
|
||||||
|
)
|
||||||
|
assert follow.approved is True
|
||||||
|
serializer = serializers.RejectFollowSerializer(
|
||||||
|
follow, context={"actor": remote_actor}
|
||||||
|
)
|
||||||
|
ii = factories["federation.InboxItem"](actor=local_actor)
|
||||||
|
routes.inbox_reject_follow(
|
||||||
|
serializer.data,
|
||||||
|
context={"actor": remote_actor, "inbox_items": [ii], "raise_exception": True},
|
||||||
|
)
|
||||||
|
follow.refresh_from_db()
|
||||||
|
assert follow.approved is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_outbox_reject_follow_library(factories):
|
||||||
|
remote_actor = factories["federation.Actor"]()
|
||||||
|
local_actor = factories["federation.Actor"](local=True)
|
||||||
|
follow = factories["federation.LibraryFollow"](
|
||||||
|
actor=remote_actor, target__actor=local_actor
|
||||||
|
)
|
||||||
|
|
||||||
|
activity = list(routes.outbox_reject_follow({"follow": follow}))[0]
|
||||||
|
|
||||||
|
serializer = serializers.RejectFollowSerializer(
|
||||||
|
follow, context={"actor": local_actor}
|
||||||
|
)
|
||||||
|
expected = serializer.data
|
||||||
|
expected["to"] = [remote_actor]
|
||||||
|
|
||||||
|
assert activity["payload"] == expected
|
||||||
|
assert activity["actor"] == local_actor
|
||||||
|
assert activity["object"] == follow
|
||||||
|
assert activity["related_object"] == follow.target
|
||||||
|
|
||||||
|
|
||||||
def test_handle_library_entry_update_can_manage(factories, mocker):
|
def test_handle_library_entry_update_can_manage(factories, mocker):
|
||||||
update_library_entity = mocker.patch(
|
update_library_entity = mocker.patch(
|
||||||
"funkwhale_api.music.tasks.update_library_entity"
|
"funkwhale_api.music.tasks.update_library_entity"
|
||||||
|
|
|
@ -318,6 +318,22 @@ def test_accept_follow_serializer_validates_on_context(factories):
|
||||||
assert "object" in serializer.errors["object"]
|
assert "object" in serializer.errors["object"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_reject_follow_serializer_representation(factories):
|
||||||
|
follow = factories["federation.Follow"](approved=None)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
"@context": jsonld.get_default_context(),
|
||||||
|
"id": follow.get_federation_id() + "/reject",
|
||||||
|
"type": "Reject",
|
||||||
|
"actor": follow.target.fid,
|
||||||
|
"object": serializers.FollowSerializer(follow).data,
|
||||||
|
}
|
||||||
|
|
||||||
|
serializer = serializers.RejectFollowSerializer(follow)
|
||||||
|
|
||||||
|
assert serializer.data == expected
|
||||||
|
|
||||||
|
|
||||||
def test_undo_follow_serializer_representation(factories):
|
def test_undo_follow_serializer_representation(factories):
|
||||||
follow = factories["federation.Follow"](approved=True)
|
follow = factories["federation.Follow"](approved=True)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Broadcast/handle rejected follows (#858)
|
Ładowanie…
Reference in New Issue