680 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			680 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
	
| import pytest
 | |
| from django.db.models import Q
 | |
| from django.urls import reverse
 | |
| 
 | |
| from funkwhale_api.federation import (
 | |
|     activity,
 | |
|     api_serializers,
 | |
|     models,
 | |
|     serializers,
 | |
|     tasks,
 | |
| )
 | |
| 
 | |
| 
 | |
| def test_receive_validates_basic_attributes_and_stores_activity(
 | |
|     mrf_inbox_registry, factories, now, mocker
 | |
| ):
 | |
|     mocker.patch.object(
 | |
|         activity.InboxRouter, "get_matching_handlers", return_value=True
 | |
|     )
 | |
|     mrf_inbox_registry_apply = mocker.spy(mrf_inbox_registry, "apply")
 | |
|     serializer_init = mocker.spy(serializers.BaseActivitySerializer, "__init__")
 | |
|     mocked_dispatch = mocker.patch("funkwhale_api.common.utils.on_commit")
 | |
|     inbox_actor = factories["federation.Actor"]()
 | |
|     local_to_actor = factories["users.User"]().create_actor()
 | |
|     local_cc_actor = factories["users.User"]().create_actor()
 | |
|     remote_actor = factories["federation.Actor"]()
 | |
|     a = {
 | |
|         "@context": [],
 | |
|         "actor": remote_actor.fid,
 | |
|         "type": "Noop",
 | |
|         "id": "https://test.activity",
 | |
|         "to": [local_to_actor.fid, remote_actor.fid],
 | |
|         "cc": [local_cc_actor.fid, activity.PUBLIC_ADDRESS],
 | |
|     }
 | |
| 
 | |
|     copy = activity.receive(
 | |
|         activity=a, on_behalf_of=remote_actor, inbox_actor=inbox_actor
 | |
|     )
 | |
|     mrf_inbox_registry_apply.assert_called_once_with(a, sender_id=a["actor"])
 | |
| 
 | |
|     assert copy.payload == a
 | |
|     assert copy.creation_date >= now
 | |
|     assert copy.actor == remote_actor
 | |
|     assert copy.fid == a["id"]
 | |
|     assert copy.type == "Noop"
 | |
|     mocked_dispatch.assert_called_once_with(
 | |
|         tasks.dispatch_inbox.delay, activity_id=copy.pk
 | |
|     )
 | |
| 
 | |
|     assert models.InboxItem.objects.count() == 3
 | |
|     for actor, t in [
 | |
|         (local_to_actor, "to"),
 | |
|         (inbox_actor, "to"),
 | |
|         (local_cc_actor, "cc"),
 | |
|     ]:
 | |
|         ii = models.InboxItem.objects.get(actor=actor)
 | |
|         assert ii.type == t
 | |
|         assert ii.activity == copy
 | |
|         assert ii.is_read is False
 | |
| 
 | |
|     assert serializer_init.call_args[1]["context"] == {
 | |
|         "actor": remote_actor,
 | |
|         "local_recipients": True,
 | |
|         "recipients": [inbox_actor],
 | |
|     }
 | |
|     assert serializer_init.call_args[1]["data"] == a
 | |
| 
 | |
| 
 | |
| def test_receive_uses_follow_object_if_no_audience_provided(
 | |
|     mrf_inbox_registry, factories, now, mocker
 | |
| ):
 | |
|     mocker.patch.object(
 | |
|         activity.InboxRouter, "get_matching_handlers", return_value=True
 | |
|     )
 | |
|     mocker.patch("funkwhale_api.common.utils.on_commit")
 | |
|     local_to_actor = factories["users.User"]().create_actor()
 | |
|     remote_actor = factories["federation.Actor"]()
 | |
|     a = {
 | |
|         "@context": [],
 | |
|         "actor": remote_actor.fid,
 | |
|         "type": "Follow",
 | |
|         "id": "https://test.activity",
 | |
|         "object": local_to_actor.fid,
 | |
|     }
 | |
| 
 | |
|     activity.receive(activity=a, on_behalf_of=remote_actor, inbox_actor=None)
 | |
| 
 | |
|     assert models.InboxItem.objects.filter(actor=local_to_actor, type="to").exists()
 | |
| 
 | |
| 
 | |
| def test_receive_uses_mrf_returned_payload(mrf_inbox_registry, factories, now, mocker):
 | |
|     mocker.patch.object(
 | |
|         activity.InboxRouter, "get_matching_handlers", return_value=True
 | |
|     )
 | |
| 
 | |
|     def patched_apply(payload, **kwargs):
 | |
|         payload["type"] = "SomethingElse"
 | |
|         return payload, True
 | |
| 
 | |
|     mrf_inbox_registry_apply = mocker.patch.object(
 | |
|         mrf_inbox_registry, "apply", side_effect=patched_apply
 | |
|     )
 | |
|     mocked_dispatch = mocker.patch("funkwhale_api.common.utils.on_commit")
 | |
|     local_to_actor = factories["users.User"]().create_actor()
 | |
|     remote_actor = factories["federation.Actor"]()
 | |
|     a = {
 | |
|         "@context": [],
 | |
|         "actor": remote_actor.fid,
 | |
|         "type": "Noop",
 | |
|         "id": "https://test.activity",
 | |
|         "to": [local_to_actor.fid],
 | |
|     }
 | |
| 
 | |
|     copy = activity.receive(activity=a, on_behalf_of=remote_actor)
 | |
|     mrf_inbox_registry_apply.assert_called_once_with(a, sender_id=a["actor"])
 | |
| 
 | |
|     expected = a.copy()
 | |
|     expected["type"] = "SomethingElse"
 | |
|     assert copy.payload == expected
 | |
|     assert copy.creation_date >= now
 | |
|     assert copy.actor == remote_actor
 | |
|     assert copy.fid == a["id"]
 | |
|     assert copy.type == "SomethingElse"
 | |
|     mocked_dispatch.assert_called_once_with(
 | |
|         tasks.dispatch_inbox.delay, activity_id=copy.pk
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_receive_mrf_skip(mrf_inbox_registry, factories, now, mocker):
 | |
|     mocker.patch.object(
 | |
|         activity.InboxRouter, "get_matching_handlers", return_value=True
 | |
|     )
 | |
|     mocker.patch.object(mrf_inbox_registry, "apply", return_value=(None, False))
 | |
|     local_to_actor = factories["users.User"]().create_actor()
 | |
|     remote_actor = factories["federation.Actor"]()
 | |
|     a = {
 | |
|         "@context": [],
 | |
|         "actor": remote_actor.fid,
 | |
|         "type": "Noop",
 | |
|         "id": "https://test.activity",
 | |
|         "to": [local_to_actor.fid],
 | |
|     }
 | |
| 
 | |
|     copy = activity.receive(activity=a, on_behalf_of=remote_actor)
 | |
|     assert copy is None
 | |
| 
 | |
| 
 | |
| def test_receive_calls_should_reject(factories, now, mocker):
 | |
|     should_reject = mocker.patch.object(activity, "should_reject", return_value=True)
 | |
|     mocker.patch.object(
 | |
|         activity.InboxRouter, "get_matching_handlers", return_value=True
 | |
|     )
 | |
|     local_to_actor = factories["users.User"]().create_actor()
 | |
|     remote_actor = factories["federation.Actor"]()
 | |
|     a = {
 | |
|         "@context": [],
 | |
|         "actor": remote_actor.fid,
 | |
|         "type": "Noop",
 | |
|         "id": "https://test.activity",
 | |
|         "to": [local_to_actor.fid, remote_actor.fid],
 | |
|     }
 | |
| 
 | |
|     copy = activity.receive(activity=a, on_behalf_of=remote_actor)
 | |
|     should_reject.assert_called_once_with(
 | |
|         fid=a["id"], actor_id=remote_actor.fid, payload=a
 | |
|     )
 | |
|     assert copy is None
 | |
| 
 | |
| 
 | |
| def test_receive_skips_if_no_matching_route(factories, now, mocker):
 | |
|     get_matching_handlers = mocker.patch.object(
 | |
|         activity.InboxRouter, "get_matching_handlers", return_value=[]
 | |
|     )
 | |
|     local_to_actor = factories["users.User"]().create_actor()
 | |
|     remote_actor = factories["federation.Actor"]()
 | |
|     a = {
 | |
|         "@context": [],
 | |
|         "actor": remote_actor.fid,
 | |
|         "type": "Noop",
 | |
|         "id": "https://test.activity",
 | |
|         "to": [local_to_actor.fid, remote_actor.fid],
 | |
|     }
 | |
| 
 | |
|     copy = activity.receive(activity=a, on_behalf_of=remote_actor)
 | |
|     get_matching_handlers.assert_called_once_with(a)
 | |
|     assert copy is None
 | |
|     assert models.Activity.objects.count() == 0
 | |
| 
 | |
| 
 | |
| def test_match_route_ignore_payload_issues():
 | |
|     payload = {"object": "http://hello"}
 | |
|     assert activity.match_route({"object.type": "Test"}, payload) is False
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "params, policy_kwargs, expected",
 | |
|     [
 | |
|         ({"fid": "https://ok.test"}, {"target_domain__name": "notok.test"}, False),
 | |
|         (
 | |
|             {"fid": "https://ok.test"},
 | |
|             {"target_domain__name": "ok.test", "is_active": False},
 | |
|             False,
 | |
|         ),
 | |
|         (
 | |
|             {"fid": "https://ok.test"},
 | |
|             {"target_domain__name": "ok.test", "block_all": False},
 | |
|             False,
 | |
|         ),
 | |
|         # id match blocked domain
 | |
|         ({"fid": "http://notok.test"}, {"target_domain__name": "notok.test"}, True),
 | |
|         # actor id match blocked domain
 | |
|         (
 | |
|             {"fid": "http://ok.test", "actor_id": "https://notok.test"},
 | |
|             {"target_domain__name": "notok.test"},
 | |
|             True,
 | |
|         ),
 | |
|         # actor id match blocked domain
 | |
|         (
 | |
|             {"fid": None, "actor_id": "https://notok.test"},
 | |
|             {"target_domain__name": "notok.test"},
 | |
|             True,
 | |
|         ),
 | |
|         # reject media
 | |
|         (
 | |
|             {
 | |
|                 "payload": {"type": "Library"},
 | |
|                 "fid": "http://ok.test",
 | |
|                 "actor_id": "http://notok.test",
 | |
|             },
 | |
|             {
 | |
|                 "target_domain__name": "notok.test",
 | |
|                 "block_all": False,
 | |
|                 "reject_media": True,
 | |
|             },
 | |
|             True,
 | |
|         ),
 | |
|     ],
 | |
| )
 | |
| def test_should_reject(factories, params, policy_kwargs, expected):
 | |
|     factories["moderation.InstancePolicy"](for_domain=True, **policy_kwargs)
 | |
| 
 | |
|     assert activity.should_reject(**params) is expected
 | |
| 
 | |
| 
 | |
| def test_get_actors_from_audience_urls(settings, db):
 | |
|     settings.FEDERATION_HOSTNAME = "federation.hostname"
 | |
|     urls = [
 | |
|         "https://wrong.url",
 | |
|         "https://federation.hostname"
 | |
|         + reverse("federation:actors-detail", kwargs={"preferred_username": "kevin"}),
 | |
|         "https://federation.hostname"
 | |
|         + reverse("federation:actors-detail", kwargs={"preferred_username": "alice"}),
 | |
|         "https://federation.hostname"
 | |
|         + reverse("federation:actors-detail", kwargs={"preferred_username": "bob"}),
 | |
|         "https://federation.hostname",
 | |
|         activity.PUBLIC_ADDRESS,
 | |
|     ]
 | |
|     followed_query = Q(target__followers_url=urls[0])
 | |
|     for url in urls[1:-1]:
 | |
|         followed_query |= Q(target__followers_url=url)
 | |
|     actor_follows = models.Follow.objects.filter(followed_query, approved=True)
 | |
|     expected = models.Actor.objects.filter(
 | |
|         Q(fid__in=urls[0:-1]) | Q(pk__in=actor_follows.values_list("actor", flat=True))
 | |
|     )
 | |
|     assert str(activity.get_actors_from_audience(urls).query) == str(expected.query)
 | |
| 
 | |
| 
 | |
| def test_receive_invalid_data(factories):
 | |
|     remote_actor = factories["federation.Actor"]()
 | |
|     a = {"@context": [], "actor": remote_actor.fid, "id": "https://test.activity"}
 | |
| 
 | |
|     with pytest.raises(serializers.serializers.ValidationError):
 | |
|         activity.receive(activity=a, on_behalf_of=remote_actor)
 | |
| 
 | |
| 
 | |
| def test_receive_actor_mismatch(factories):
 | |
|     remote_actor = factories["federation.Actor"]()
 | |
|     a = {
 | |
|         "@context": [],
 | |
|         "type": "Noop",
 | |
|         "actor": "https://hello",
 | |
|         "id": "https://test.activity",
 | |
|     }
 | |
| 
 | |
|     with pytest.raises(serializers.serializers.ValidationError):
 | |
|         activity.receive(activity=a, on_behalf_of=remote_actor)
 | |
| 
 | |
| 
 | |
| def test_inbox_routing(factories, mocker):
 | |
|     object = factories["music.Artist"]()
 | |
|     target = factories["music.Artist"]()
 | |
|     router = activity.InboxRouter()
 | |
|     a = factories["federation.Activity"](type="Follow")
 | |
| 
 | |
|     handler_payload = {}
 | |
|     handler_context = {}
 | |
| 
 | |
|     def handler(payload, context):
 | |
|         handler_payload.update(payload)
 | |
|         handler_context.update(context)
 | |
|         return {"target": target, "object": object}
 | |
| 
 | |
|     router.connect({"type": "Follow"}, handler)
 | |
| 
 | |
|     good_message = {"type": "Follow"}
 | |
|     router.dispatch(good_message, context={"activity": a})
 | |
| 
 | |
|     assert handler_payload == good_message
 | |
|     assert handler_context == {"activity": a}
 | |
| 
 | |
|     a.refresh_from_db()
 | |
| 
 | |
|     assert a.object == object
 | |
|     assert a.target == target
 | |
| 
 | |
| 
 | |
| def test_inbox_routing_no_handler(factories, mocker):
 | |
|     router = activity.InboxRouter()
 | |
|     a = factories["federation.Activity"](type="Follow")
 | |
|     handler = mocker.Mock()
 | |
|     router.connect({"type": "Follow"}, handler)
 | |
| 
 | |
|     router.dispatch({"type": "Follow"}, context={"activity": a}, call_handlers=False)
 | |
|     handler.assert_not_called()
 | |
| 
 | |
| 
 | |
| def test_inbox_routing_send_to_channel(factories, mocker):
 | |
|     group_send = mocker.patch("funkwhale_api.common.channels.group_send")
 | |
|     a = factories["federation.Activity"](type="Follow")
 | |
|     ii = factories["federation.InboxItem"](actor__local=True)
 | |
| 
 | |
|     router = activity.InboxRouter()
 | |
|     handler = mocker.stub()
 | |
|     router.connect({"type": "Follow"}, handler)
 | |
|     good_message = {"type": "Follow"}
 | |
|     router.dispatch(
 | |
|         good_message, context={"activity": a, "inbox_items": ii.__class__.objects.all()}
 | |
|     )
 | |
| 
 | |
|     ii.refresh_from_db()
 | |
| 
 | |
|     group_send.assert_called_once_with(
 | |
|         f"user.{ii.actor.user.pk}.inbox",
 | |
|         {
 | |
|             "type": "event.send",
 | |
|             "text": "",
 | |
|             "data": {
 | |
|                 "type": "inbox.item_added",
 | |
|                 "item": api_serializers.InboxItemSerializer(ii).data,
 | |
|             },
 | |
|         },
 | |
|     )
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "route,payload,expected",
 | |
|     [
 | |
|         ({"type": "Follow"}, {"type": "Follow"}, True),
 | |
|         ({"type": "Follow"}, {"type": "Noop"}, False),
 | |
|         ({"type": "Follow"}, {"type": "Follow", "id": "https://hello"}, True),
 | |
|         (
 | |
|             {"type": "Create", "object.type": "Audio"},
 | |
|             {"type": "Create", "object": {"type": "Note"}},
 | |
|             False,
 | |
|         ),
 | |
|         (
 | |
|             {"type": "Create", "object.type": "Audio"},
 | |
|             {"type": "Create", "object": {"type": "Audio"}},
 | |
|             True,
 | |
|         ),
 | |
|     ],
 | |
| )
 | |
| def test_route_matching(route, payload, expected):
 | |
|     assert activity.match_route(route, payload) is expected
 | |
| 
 | |
| 
 | |
| def test_outbox_router_dispatch(mocker, factories, preferences, now):
 | |
|     router = activity.OutboxRouter()
 | |
|     actor = factories["federation.Actor"]()
 | |
|     r1 = factories["federation.Actor"]()
 | |
|     r2 = factories["federation.Actor"]()
 | |
|     mocked_dispatch = mocker.patch("funkwhale_api.common.utils.on_commit")
 | |
| 
 | |
|     def handler(context):
 | |
|         yield {
 | |
|             "payload": {
 | |
|                 "type": "Noop",
 | |
|                 "actor": actor.fid,
 | |
|                 "summary": context["summary"],
 | |
|                 "to": [r1],
 | |
|                 "cc": [r2, activity.PUBLIC_ADDRESS],
 | |
|             },
 | |
|             "actor": actor,
 | |
|         }
 | |
| 
 | |
|     router.connect({"type": "Noop"}, handler)
 | |
|     activities = router.dispatch({"type": "Noop"}, {"summary": "hello"})
 | |
|     a = activities[0]
 | |
| 
 | |
|     mocked_dispatch.assert_called_once_with(
 | |
|         tasks.dispatch_outbox.delay, activity_id=a.pk
 | |
|     )
 | |
| 
 | |
|     assert a.payload == {
 | |
|         "type": "Noop",
 | |
|         "actor": actor.fid,
 | |
|         "summary": "hello",
 | |
|         "to": [r1.fid],
 | |
|         "cc": [r2.fid, activity.PUBLIC_ADDRESS],
 | |
|     }
 | |
|     assert a.actor == actor
 | |
|     assert a.creation_date >= now
 | |
|     assert a.uuid is not None
 | |
| 
 | |
|     assert a.deliveries.count() == 2
 | |
|     for actor in [r1, r2]:
 | |
|         delivery = a.deliveries.get(inbox_url=actor.inbox_url)
 | |
|         assert delivery.is_delivered is False
 | |
| 
 | |
| 
 | |
| def test_outbox_router_dispatch_allow_list(mocker, factories, preferences, now):
 | |
|     preferences["moderation__allow_list_enabled"] = True
 | |
|     router = activity.OutboxRouter()
 | |
|     actor = factories["federation.Actor"]()
 | |
|     r1 = factories["federation.Actor"](domain__allowed=True)
 | |
|     r2 = factories["federation.Actor"]()
 | |
|     prepare_deliveries_and_inbox_items = mocker.spy(
 | |
|         activity, "prepare_deliveries_and_inbox_items"
 | |
|     )
 | |
| 
 | |
|     def handler(context):
 | |
|         yield {
 | |
|             "payload": {
 | |
|                 "type": "Noop",
 | |
|                 "actor": actor.fid,
 | |
|                 "summary": context["summary"],
 | |
|                 "to": [r1],
 | |
|                 "cc": [r2],
 | |
|             },
 | |
|             "actor": actor,
 | |
|         }
 | |
| 
 | |
|     router.connect({"type": "Noop"}, handler)
 | |
|     router.dispatch({"type": "Noop"}, {"summary": "hello"})
 | |
|     prepare_deliveries_and_inbox_items.assert_any_call(
 | |
|         [r1], "to", allowed_domains={r1.domain_id}
 | |
|     )
 | |
|     prepare_deliveries_and_inbox_items.assert_any_call(
 | |
|         [r2], "cc", allowed_domains={r1.domain_id}
 | |
|     )
 | |
| 
 | |
| 
 | |
| def test_prepare_deliveries_and_inbox_items(factories, preferences):
 | |
|     local_actor1 = factories["federation.Actor"](
 | |
|         local=True, shared_inbox_url="https://testlocal.inbox"
 | |
|     )
 | |
|     local_actor2 = factories["federation.Actor"](
 | |
|         local=True, shared_inbox_url=local_actor1.shared_inbox_url
 | |
|     )
 | |
|     local_actor3 = factories["federation.Actor"](local=True, shared_inbox_url=None)
 | |
| 
 | |
|     remote_actor1 = factories["federation.Actor"](
 | |
|         shared_inbox_url="https://testremote.inbox"
 | |
|     )
 | |
|     remote_actor2 = factories["federation.Actor"](
 | |
|         shared_inbox_url=remote_actor1.shared_inbox_url
 | |
|     )
 | |
|     remote_actor3 = factories["federation.Actor"](shared_inbox_url=None)
 | |
|     remote_actor4 = factories["federation.Actor"]()
 | |
|     # follow not approved
 | |
|     factories["federation.Follow"](
 | |
|         target=remote_actor3, actor__local=False, approved=False
 | |
|     )
 | |
| 
 | |
|     followed_actor = factories["federation.Actor"]()
 | |
|     actor_follower_local = factories["federation.Follow"](
 | |
|         target=followed_actor, actor__local=True, approved=True
 | |
|     ).actor
 | |
|     actor_follower_remote = factories["federation.Follow"](
 | |
|         target=followed_actor, actor__local=False, approved=True
 | |
|     ).actor
 | |
|     # follow not approved
 | |
|     factories["federation.Follow"](
 | |
|         target=followed_actor, actor__local=False, approved=False
 | |
|     )
 | |
| 
 | |
|     recipients = [
 | |
|         local_actor1,
 | |
|         local_actor2,
 | |
|         local_actor3,
 | |
|         remote_actor1,
 | |
|         remote_actor2,
 | |
|         remote_actor3,
 | |
|         activity.PUBLIC_ADDRESS,
 | |
|         {"type": "followers", "target": followed_actor},
 | |
|         {"type": "actor_inbox", "actor": remote_actor4},
 | |
|     ]
 | |
| 
 | |
|     inbox_items, deliveries, urls = activity.prepare_deliveries_and_inbox_items(
 | |
|         recipients, "to", allowed_domains=None
 | |
|     )
 | |
|     expected_inbox_items = sorted(
 | |
|         [
 | |
|             models.InboxItem(actor=local_actor1, type="to"),
 | |
|             models.InboxItem(actor=local_actor2, type="to"),
 | |
|             models.InboxItem(actor=local_actor3, type="to"),
 | |
|             models.InboxItem(actor=actor_follower_local, type="to"),
 | |
|         ],
 | |
|         key=lambda v: v.actor.pk,
 | |
|     )
 | |
| 
 | |
|     expected_deliveries = sorted(
 | |
|         [
 | |
|             models.Delivery(inbox_url=remote_actor1.shared_inbox_url),
 | |
|             models.Delivery(inbox_url=remote_actor3.inbox_url),
 | |
|             models.Delivery(inbox_url=remote_actor4.inbox_url),
 | |
|             models.Delivery(inbox_url=actor_follower_remote.inbox_url),
 | |
|         ],
 | |
|         key=lambda v: v.inbox_url,
 | |
|     )
 | |
| 
 | |
|     expected_urls = [
 | |
|         local_actor1.fid,
 | |
|         local_actor2.fid,
 | |
|         local_actor3.fid,
 | |
|         remote_actor1.fid,
 | |
|         remote_actor2.fid,
 | |
|         remote_actor3.fid,
 | |
|         activity.PUBLIC_ADDRESS,
 | |
|         followed_actor.followers_url,
 | |
|         remote_actor4.fid,
 | |
|     ]
 | |
| 
 | |
|     assert urls == expected_urls
 | |
|     assert len(expected_inbox_items) == len(inbox_items)
 | |
|     assert len(expected_deliveries) == len(deliveries)
 | |
| 
 | |
|     for delivery, expected_delivery in zip(
 | |
|         sorted(deliveries, key=lambda v: v.inbox_url), expected_deliveries
 | |
|     ):
 | |
|         assert delivery.inbox_url == expected_delivery.inbox_url
 | |
| 
 | |
|     for inbox_item, expected_inbox_item in zip(
 | |
|         sorted(inbox_items, key=lambda v: v.actor.pk), expected_inbox_items
 | |
|     ):
 | |
|         assert inbox_item.actor == expected_inbox_item.actor
 | |
|         assert inbox_item.type == "to"
 | |
| 
 | |
| 
 | |
| def test_prepare_deliveries_and_inbox_items_allow_list(factories, preferences):
 | |
|     preferences["moderation__allow_list_enabled"] = True
 | |
|     remote_actor1 = factories["federation.Actor"](domain__allowed=True)
 | |
|     remote_actor2 = factories["federation.Actor"](domain__allowed=False)
 | |
| 
 | |
|     recipients = [remote_actor1, remote_actor2]
 | |
| 
 | |
|     inbox_items, deliveries, urls = activity.prepare_deliveries_and_inbox_items(
 | |
|         recipients, "to", allowed_domains={remote_actor1.domain_id}
 | |
|     )
 | |
|     expected_inbox_items = []
 | |
| 
 | |
|     expected_deliveries = [models.Delivery(inbox_url=remote_actor1.inbox_url)]
 | |
| 
 | |
|     expected_urls = [remote_actor1.fid]
 | |
| 
 | |
|     assert urls == expected_urls
 | |
|     assert len(expected_inbox_items) == len(inbox_items)
 | |
|     assert len(expected_deliveries) == len(deliveries)
 | |
| 
 | |
|     for delivery, expected_delivery in zip(
 | |
|         sorted(deliveries, key=lambda v: v.inbox_url), expected_deliveries
 | |
|     ):
 | |
|         assert delivery.inbox_url == expected_delivery.inbox_url
 | |
| 
 | |
| 
 | |
| def test_prepare_deliveries_and_inbox_items_instances_with_followers(factories):
 | |
|     domain1 = factories["federation.Domain"](with_service_actor=True)
 | |
|     domain2 = factories["federation.Domain"](with_service_actor=True)
 | |
|     library = factories["music.Library"](actor__local=True)
 | |
| 
 | |
|     factories["federation.LibraryFollow"](
 | |
|         target=library, actor__local=True, approved=True
 | |
|     ).actor
 | |
|     library_follower_remote = factories["federation.LibraryFollow"](
 | |
|         target=library, actor__domain=domain1, approved=True
 | |
|     ).actor
 | |
| 
 | |
|     followed_actor = factories["federation.Actor"](local=True)
 | |
|     factories["federation.Follow"](
 | |
|         target=followed_actor, actor__local=True, approved=True
 | |
|     ).actor
 | |
|     actor_follower_remote = factories["federation.Follow"](
 | |
|         target=followed_actor, actor__domain=domain2, approved=True
 | |
|     ).actor
 | |
| 
 | |
|     recipients = [activity.PUBLIC_ADDRESS, {"type": "instances_with_followers"}]
 | |
| 
 | |
|     inbox_items, deliveries, urls = activity.prepare_deliveries_and_inbox_items(
 | |
|         recipients, "to", allowed_domains=None
 | |
|     )
 | |
| 
 | |
|     expected_deliveries = sorted(
 | |
|         [
 | |
|             models.Delivery(
 | |
|                 inbox_url=library_follower_remote.domain.service_actor.inbox_url
 | |
|             ),
 | |
|             models.Delivery(
 | |
|                 inbox_url=actor_follower_remote.domain.service_actor.inbox_url
 | |
|             ),
 | |
|         ],
 | |
|         key=lambda v: v.inbox_url,
 | |
|     )
 | |
|     assert inbox_items == []
 | |
|     assert len(expected_deliveries) == len(deliveries)
 | |
| 
 | |
|     for delivery, expected_delivery in zip(
 | |
|         sorted(deliveries, key=lambda v: v.inbox_url), expected_deliveries
 | |
|     ):
 | |
|         assert delivery.inbox_url == expected_delivery.inbox_url
 | |
| 
 | |
| 
 | |
| @pytest.mark.parametrize(
 | |
|     "url, allowed_domains, expected",
 | |
|     [
 | |
|         ("https://domain.example/test", None, True),
 | |
|         ("https://domain.example/test", [], False),
 | |
|         ("https://allowed.example/test", ["allowed.example"], True),
 | |
|         ("https://domain.example/test", ["allowed.example"], False),
 | |
|         ("https://social.allowed.example/test", ["allowed.example"], False),
 | |
|     ],
 | |
| )
 | |
| def test_is_allowed_url(url, allowed_domains, expected):
 | |
|     assert activity.is_allowed_url(url, allowed_domains) is expected
 | |
| 
 | |
| 
 | |
| def test_should_rotate_actor_key(settings, cache, now):
 | |
|     actor_id = 42
 | |
|     settings.ACTOR_KEY_ROTATION_DELAY = 10
 | |
| 
 | |
|     cache.set(activity.ACTOR_KEY_ROTATION_LOCK_CACHE_KEY.format(actor_id), True)
 | |
| 
 | |
|     assert activity.should_rotate_actor_key(actor_id) is False
 | |
| 
 | |
|     cache.delete(activity.ACTOR_KEY_ROTATION_LOCK_CACHE_KEY.format(actor_id))
 | |
| 
 | |
|     assert activity.should_rotate_actor_key(actor_id) is True
 | |
| 
 | |
| 
 | |
| def test_schedule_key_rotation(cache, mocker):
 | |
|     actor_id = 42
 | |
|     rotate_actor_key = mocker.patch.object(tasks.rotate_actor_key, "apply_async")
 | |
| 
 | |
|     activity.schedule_key_rotation(actor_id, 3600)
 | |
|     rotate_actor_key.assert_called_once_with(
 | |
|         kwargs={"actor_id": actor_id}, countdown=3600
 | |
|     )
 | |
|     assert cache.get(activity.ACTOR_KEY_ROTATION_LOCK_CACHE_KEY.format(actor_id), True)
 | |
| 
 | |
| 
 | |
| def test_outbox_dispatch_rotate_key_on_delete(
 | |
|     mocker, factories, cache, settings, preferences
 | |
| ):
 | |
|     router = activity.OutboxRouter()
 | |
|     actor = factories["federation.Actor"]()
 | |
|     r1 = factories["federation.Actor"]()
 | |
|     schedule_key_rotation = mocker.spy(activity, "schedule_key_rotation")
 | |
| 
 | |
|     def handler(context):
 | |
|         yield {
 | |
|             "payload": {"type": "Delete", "actor": actor.fid, "to": [r1]},
 | |
|             "actor": actor,
 | |
|         }
 | |
| 
 | |
|     router.connect({"type": "Delete"}, handler)
 | |
|     router.dispatch({"type": "Delete"}, {})
 | |
| 
 | |
|     schedule_key_rotation.assert_called_once_with(
 | |
|         actor.id, settings.ACTOR_KEY_ROTATION_DELAY
 | |
|     )
 |