Ejemplo n.º 1
0
def send_profile(profile_id, recipients=None):
    """Handle sending a Profile object out via the federation layer.

    :param profile_id: Profile.id of profile to send
    :param recipients: Optional list of recipients, see `federation.outbound.handle_send` parameters
    """
    try:
        profile = Profile.objects.get(id=profile_id, user__isnull=False)
    except Profile.DoesNotExist:
        logger.warning("send_profile - No local profile found with id %s",
                       profile_id)
        return
    entity = make_federable_profile(profile)
    if not entity:
        logger.warning("send_profile - No entity for %s", profile)
        return
    if settings.DEBUG:
        # Don't send in development mode
        return
    if not recipients:
        recipients = _get_remote_followers(profile, profile.visibility)
    logger.debug("send_profile - sending to recipients: %s", recipients)
    handle_send(entity,
                profile.federable,
                recipients,
                payload_logger=get_outbound_payload_logger())
Ejemplo n.º 2
0
    def test_no_error_for_diaspora_entities_on_activitypub_recipients(
            self, mock_logger, mock_send, diasporacomment):
        key = get_dummy_private_key()
        diasporacomment.outbound_doc = diasporacomment.to_xml()
        recipients = [{
            "endpoint": "https://example.com/receive/public",
            "public": True,
            "protocol": "diaspora",
            "fid": "",
        }, {
            "endpoint": "https://example.net/inbox",
            "fid": "https://example.net/foobar",
            "public": True,
            "protocol": "activitypub",
        }]
        author = UserType(
            private_key=key,
            id="*****@*****.**",
            handle="*****@*****.**",
        )
        handle_send(diasporacomment, author, recipients)

        # Ensure first call is a public diaspora payload
        args, kwargs = mock_send.call_args_list[0]
        assert args[0] == "https://example.com/receive/public"

        # Should only be one call
        assert mock_send.call_count == 1

        # Ensure no error logged
        assert mock_logger.call_count == 0
Ejemplo n.º 3
0
    def test_calls_handle_create_payload(self, mock_send, diasporapost):
        key = get_dummy_private_key()
        recipients = [
            ("diaspora://[email protected]/profile/xyz", key.publickey()),
            ("diaspora://foo@localhost/profile/abc", None),
            "diaspora://[email protected]/profile/zzz",
            "diaspora://[email protected]/profile/qwerty",  # Same host twice to ensure one delivery only per host
            # for public payloads
        ]
        mock_author = Mock(private_key=key, handle="*****@*****.**")
        handle_send(diasporapost, mock_author, recipients)

        # Ensure first call is a private payload
        args, kwargs = mock_send.call_args_list[0]
        assert args[0] == "https://127.0.0.1/receive/users/xyz"
        assert "aes_key" in args[1]
        assert "encrypted_magic_envelope" in args[1]
        assert kwargs['headers'] == {'Content-Type': 'application/json'}

        # Ensure public payloads and recipients, one per unique host
        args1, kwargs1 = mock_send.call_args_list[1]
        args2, kwargs2 = mock_send.call_args_list[2]
        public_endpoints = {args1[0], args2[0]}
        assert public_endpoints == {
            "https://example.net/receive/public",
            "https://localhost/receive/public"
        }
        assert args1[1].startswith("<me:env xmlns:me=")
        assert args2[1].startswith("<me:env xmlns:me=")
        assert kwargs1['headers'] == {
            'Content-Type': 'application/magic-envelope+xml'
        }
        assert kwargs2['headers'] == {
            'Content-Type': 'application/magic-envelope+xml'
        }
Ejemplo n.º 4
0
def send_reply(content_id):
    """Handle sending a Content object that is a reply out via the federation layer.

    Currently we only deliver public content.
    """
    try:
        content = Content.objects.get(id=content_id,
                                      visibility=Visibility.PUBLIC,
                                      parent_id__isnull=False)
    except Content.DoesNotExist:
        logger.warning("No content found with id %s", content_id)
        return
    if not content.is_local:
        return
    entity = make_federable_entity(content)
    if entity:
        if settings.DEBUG:
            # Don't send in development mode
            return
        recipients = [
            (settings.SOCIALHOME_RELAY_DOMAIN, "diaspora"),
        ]
        recipients.extend(_get_remote_participants_for_parent(content.parent))
        recipients.extend(_get_remote_followers(content.author))
        handle_send(entity, content.author, recipients)
    else:
        logger.warning("No entity for %s", content)
Ejemplo n.º 5
0
def forward_relayable(entity, parent_id):
    """Handle forwarding of a relayable object.

    Currently only for public content.
    """
    try:
        parent = Content.objects.get(id=parent_id,
                                     visibility=Visibility.PUBLIC)
    except Content.DoesNotExist:
        logger.warning("No public content found with id %s", parent_id)
        return
    try:
        content = Content.objects.get(guid=entity.guid,
                                      visibility=Visibility.PUBLIC)
    except Content.DoesNotExist:
        logger.warning("forward_relayable - No content found with guid %s",
                       entity.guid)
        return
    if settings.DEBUG:
        # Don't send in development mode
        return
    recipients = [
        (settings.SOCIALHOME_RELAY_DOMAIN, "diaspora"),
    ]
    recipients.extend(
        _get_remote_participants_for_parent(parent, exclude=entity.handle))
    recipients.extend(
        _get_remote_followers(parent.author, exclude=entity.handle))
    handle_send(entity, content.author, recipients, parent_user=parent.author)
Ejemplo n.º 6
0
def send_follow_change(profile_id, followed_id, follow):
    """Handle sending of a local follow of a remote profile."""
    try:
        profile = Profile.objects.get(id=profile_id, user__isnull=False)
    except Profile.DoesNotExist:
        logger.warning(
            "send_follow_change - No local profile %s found to send follow with",
            profile_id)
        return
    try:
        remote_profile = Profile.objects.get(id=followed_id, user__isnull=True)
    except Profile.DoesNotExist:
        logger.warning(
            "send_follow_change - No remote profile %s found to send follow for",
            followed_id)
        return
    if settings.DEBUG:
        # Don't send in development mode
        return
    entity = base.Follow(
        actor_id=profile.fid,
        target_id=remote_profile.fid,
        following=follow,
        handle=profile.handle,
        target_handle=remote_profile.handle,
    )
    recipients = [
        # TODO fid or handle?
        (remote_profile.handle, remote_profile.key, remote_profile.guid),
    ]
    logger.debug("send_follow_change - sending to recipients: %s", recipients)
    handle_send(entity, profile.federable, recipients)
    # Also trigger a profile send
    # TODO fid or handle?
    send_profile(profile_id, recipients=[remote_profile.handle])
Ejemplo n.º 7
0
def send_share(content_id):
    """Handle sending a share of a Content object to the federation layer.

    Currently we only deliver public shares.
    """
    try:
        content = Content.objects.get(id=content_id,
                                      visibility=Visibility.PUBLIC,
                                      content_type=ContentType.SHARE,
                                      local=True)
    except Content.DoesNotExist:
        logger.warning("No local share found with id %s", content_id)
        return
    entity = make_federable_content(content)
    if entity:
        if settings.DEBUG:
            # Don't send in development mode
            return
        recipients = _get_remote_followers(content.author)
        if not content.share_of.local:
            # Send to original author
            recipients.append(
                # TODO fid or handle?
                content.share_of.author.handle, )
        logger.debug("send_share - sending to recipients: %s", recipients)
        handle_send(entity, content.author.federable, recipients)
    else:
        logger.warning("send_share - No entity for %s", content)
Ejemplo n.º 8
0
def forward_entity(entity, target_content_id):
    """Handle forwarding of an entity related to a target content.

    For example: remote replies on local content, remote shares on local content.
    """
    try:
        target_content = Content.objects.get(
            id=target_content_id,
            visibility__in=(Visibility.PUBLIC, Visibility.LIMITED),
            local=True,
        )
    except Content.DoesNotExist:
        logger.warning("forward_entity - No local content found with id %s", target_content_id)
        return
    try:
        content = Content.objects.fed(entity.id, visibility__in=(Visibility.PUBLIC, Visibility.LIMITED)).get()
    except Content.DoesNotExist:
        logger.warning("forward_entity - No content found with uuid %s", entity.id)
        return
    if settings.DEBUG:
        # Don't send in development mode
        return
    if target_content.visibility == Visibility.PUBLIC:
        recipients = _get_remote_participants_for_content(target_content, exclude=entity.actor_id)
        recipients.extend(_get_remote_followers(
            target_content.author,
            target_content.visibility,
            exclude=entity.actor_id,
        ))
    elif target_content.visibility == Visibility.LIMITED and content.content_type == ContentType.REPLY:
        recipients = _get_limited_recipients(entity.actor_id, target_content)
    else:
        return
    logger.debug("forward_entity - sending to recipients: %s", recipients)
    handle_send(entity, content.author.federable, recipients, parent_user=target_content.author.federable)
Ejemplo n.º 9
0
def send_profile_retraction(profile):
    """Handle sending of retractions for profiles.

    Only sent for public and limited profiles. Reason: we might actually leak user information
    outside for profiles which were never federated outside if we send for example
    SELF or SITE profile retractions.

    This must be called as a pre_delete signal or it will fail.
    """
    if profile.visibility not in (Visibility.PUBLIC,
                                  Visibility.LIMITED) or not profile.is_local:
        return
    entity = make_federable_retraction(profile)
    if entity:
        if settings.DEBUG:
            # Don't send in development mode
            return
        if profile.visibility == Visibility.PUBLIC:
            recipients = [settings.SOCIALHOME_RELAY_ID]
        else:
            recipients = []
        recipients.extend(_get_remote_followers(profile))
        logger.debug("send_profile_retraction - sending to recipients: %s",
                     recipients)
        handle_send(entity, profile.federable, recipients)
    else:
        logger.warning("send_profile_retraction - No retraction entity for %s",
                       profile)
Ejemplo n.º 10
0
def forward_entity(entity, target_content_id):
    """Handle forwarding of an entity related to a target content.

    For example: remote replies on local content, remote shares on local content.

    Currently only for public content.
    """
    try:
        target_content = Content.objects.get(id=target_content_id,
                                             visibility=Visibility.PUBLIC,
                                             local=True)
    except Content.DoesNotExist:
        logger.warning(
            "forward_entity - No public local content found with id %s",
            target_content_id)
        return
    try:
        content = Content.objects.get(guid=entity.guid,
                                      visibility=Visibility.PUBLIC)
    except Content.DoesNotExist:
        logger.warning("forward_entity - No content found with guid %s",
                       entity.guid)
        return
    if settings.DEBUG:
        # Don't send in development mode
        return
    recipients = _get_remote_participants_for_content(target_content,
                                                      exclude=entity.handle)
    recipients.extend(
        _get_remote_followers(target_content.author, exclude=entity.handle))
    handle_send(entity,
                content.author,
                recipients,
                parent_user=target_content.author)
Ejemplo n.º 11
0
def send_content(content_id):
    """Handle sending a Content object out via the federation layer.

    Currently we only deliver public content.
    """
    try:
        content = Content.objects.get(id=content_id,
                                      visibility=Visibility.PUBLIC,
                                      content_type=ContentType.CONTENT,
                                      local=True)
    except Content.DoesNotExist:
        logger.warning("No local content found with id %s", content_id)
        return
    entity = make_federable_content(content)
    if entity:
        if settings.DEBUG:
            # Don't send in development mode
            return
        recipients = [
            (settings.SOCIALHOME_RELAY_DOMAIN, "diaspora"),
        ]
        recipients.extend(_get_remote_followers(content.author))
        handle_send(entity, content.author, recipients)
    else:
        logger.warning("send_content - No entity for %s", content)
Ejemplo n.º 12
0
def send_follow_change(profile_id, followed_id, follow):
    """Handle sending of a local follow of a remote profile."""
    try:
        profile = Profile.objects.get(id=profile_id, user__isnull=False)
    except Profile.DoesNotExist:
        logger.warning("send_follow_change - No local profile %s found to send follow with", profile_id)
        return
    try:
        remote_profile = Profile.objects.get(id=followed_id, user__isnull=True)
    except Profile.DoesNotExist:
        logger.warning("send_follow_change - No remote profile %s found to send follow for", followed_id)
        return
    if settings.DEBUG:
        # Don't send in development mode
        return
    entity = base.Follow(
        activity_id=f'{profile.fid}#follow-{uuid4()}',
        actor_id=profile.fid,
        target_id=remote_profile.fid,
        following=follow,
        handle=profile.handle,
        target_handle=remote_profile.handle,
    )
    # Explicitly use limited visibility to force private endpoint
    recipients = [remote_profile.get_recipient_for_visibility(Visibility.LIMITED)]
    logger.debug("send_follow_change - sending to recipients: %s", recipients)
    handle_send(entity, profile.federable, recipients)
    # Also trigger a profile send
    send_profile(profile_id, recipients=recipients)
Ejemplo n.º 13
0
def send_reply(content_id):
    """Handle sending a Content object that is a reply out via the federation layer.

    Currently we only deliver public content.
    """
    try:
        content = Content.objects.get(id=content_id,
                                      visibility=Visibility.PUBLIC,
                                      content_type=ContentType.REPLY,
                                      local=True)
    except Content.DoesNotExist:
        logger.warning("No content found with id %s", content_id)
        return
    entity = make_federable_content(content)
    if not entity:
        logger.warning("send_reply - No entity for %s", content)
    if settings.DEBUG:
        # Don't send in development mode
        return
    # Send directly (remote parent) or as a relayable (local parent)
    if content.parent.local:
        forward_entity(entity, content.parent.id)
    else:
        # We only need to send to the original author
        recipients = [
            (content.parent.author.handle, None),
        ]
        handle_send(entity, content.author, recipients)
Ejemplo n.º 14
0
def send_profile(profile_id, recipients=None):
    """Handle sending a Profile object out via the federation layer.

    :param profile_id: Profile.id of profile to send
    :param recipients: Optional list of recipients, see `federation.outbound.handle_send` parameters
    """
    try:
        profile = Profile.objects.get(id=profile_id, user__isnull=False)
    except Profile.DoesNotExist:
        logger.warning("send_profile - No local profile found with id %s",
                       profile_id)
        return
    entity = make_federable_profile(profile)
    if not entity:
        logger.warning("send_profile - No entity for %s", profile)
        return
    if settings.DEBUG:
        # Don't send in development mode
        return
    if not recipients:
        # If we have Matrix support enabled, also add the appservice
        if settings.SOCIALHOME_MATRIX_ENABLED:
            recipients = [profile.get_recipient_for_matrix_appservice()]
        else:
            recipients = []
        recipients.extend(_get_remote_followers(profile, profile.visibility))

    logger.debug("send_profile - sending to recipients: %s", recipients)
    handle_send(entity,
                profile.federable,
                recipients,
                payload_logger=get_outbound_payload_logger())
Ejemplo n.º 15
0
def send_content_retraction(content, author_id):
    """
    Handle sending of retractions for content.
    """
    if content.visibility not in (Visibility.PUBLIC,
                                  Visibility.LIMITED) or not content.local:
        return
    author = Profile.objects.get(id=author_id)
    entity = make_federable_retraction(content, author)
    if entity:
        if settings.DEBUG:
            # Don't send in development mode
            return
        if content.visibility == Visibility.PUBLIC:
            recipients = [settings.SOCIALHOME_RELAY_ID]
            recipients.extend(_get_remote_followers(author))
        else:
            recipients = _get_limited_recipients(author.uuid, content)

        logger.debug("send_content_retraction - sending to recipients: %s",
                     recipients)
        handle_send(entity, author.federable, recipients)
    else:
        logger.warning("send_content_retraction - No retraction entity for %s",
                       content)
Ejemplo n.º 16
0
def send_content(content_id, activity_fid, recipient_id=None):
    """
    Handle sending a Content object out via the federation layer.
    """
    try:
        content = Content.objects.get(
            id=content_id,
            visibility__in=(Visibility.PUBLIC, Visibility.LIMITED),
            content_type=ContentType.CONTENT,
            local=True,
        )
    except Content.DoesNotExist:
        logger.warning("No local content found with id %s", content_id)
        return
    if recipient_id:
        try:
            recipient = Profile.objects.get(id=recipient_id, user__isnull=True)
        except Profile.DoesNotExist:
            logger.warning("No remote recipient found with id %s",
                           recipient_id)
            return
    else:
        recipient = None
    entity = make_federable_content(content)
    if entity:
        entity.activity_id = activity_fid
        if settings.DEBUG:
            # Don't send in development mode
            return
        recipients = []
        if recipient:
            recipients.append(
                recipient.get_recipient_for_visibility(content.visibility))
        else:
            if content.visibility == Visibility.PUBLIC:
                # If we have Matrix support enabled, also add the appservice
                if settings.SOCIALHOME_MATRIX_ENABLED:
                    recipients.append(
                        content.author.get_recipient_for_matrix_appservice())
                if settings.SOCIALHOME_RELAY_ID:
                    recipients.append({
                        "endpoint": settings.SOCIALHOME_RELAY_ID,
                        "fid": "",
                        "public": True,
                        "protocol": "diaspora"
                    })
            recipients.extend(
                _get_remote_followers(content.author, content.visibility))

        logger.debug("send_content - sending to recipients: %s", recipients)
        handle_send(entity,
                    content.author.federable,
                    recipients,
                    payload_logger=get_outbound_payload_logger())
    else:
        logger.warning("send_content - No entity for %s", content)
Ejemplo n.º 17
0
 def test_calls_send_document(self, mock_send, mock_create, diasporapost):
     recipients = [("[email protected]", "diaspora"), ("localhost", None)]
     mock_from_user = Mock()
     handle_send(diasporapost, mock_from_user, recipients)
     call_args_list = [
         call("https://127.0.0.1/receive/public", "payload"),
         call("https://localhost/receive/public", "payload"),
     ]
     assert call_args_list[0] in mock_send.call_args_list
     assert call_args_list[1] in mock_send.call_args_list
Ejemplo n.º 18
0
    def post_receive(self) -> None:
        """
        Post receive hook - send back follow ack.
        """
        super().post_receive()
        if not self.following:
            return

        from federation.utils.activitypub import retrieve_and_parse_profile  # Circulars
        try:
            from federation.utils.django import get_function_from_config
        except ImportError:
            logger.warning(
                "ActivitypubFollow.post_receive - Unable to send automatic Accept back, only supported on "
                "Django currently")
            return
        get_private_key_function = get_function_from_config(
            "get_private_key_function")
        key = get_private_key_function(self.target_id)
        if not key:
            logger.warning(
                "ActivitypubFollow.post_receive - Failed to send automatic Accept back: could not find "
                "profile to sign it with")
            return
        accept = ActivitypubAccept(
            activity_id=f"{self.target_id}#accept-{uuid.uuid4()}",
            actor_id=self.target_id,
            target_id=self.activity_id,
            object=self.to_as2(),
        )
        try:
            profile = retrieve_and_parse_profile(self.actor_id)
        except Exception:
            profile = None
        if not profile:
            logger.warning(
                "ActivitypubFollow.post_receive - Failed to fetch remote profile for sending back Accept"
            )
            return
        try:
            handle_send(
                accept,
                UserType(id=self.target_id, private_key=key),
                recipients=[{
                    "endpoint": profile.inboxes["private"],
                    "fid": self.actor_id,
                    "protocol": "activitypub",
                    "public": False,
                }],
            )
        except Exception:
            logger.exception(
                "ActivitypubFollow.post_receive - Failed to send Accept back")
Ejemplo n.º 19
0
def send_reply(content_id, activity_fid):
    """
    Handle sending a Content object that is a reply out via the federation layer.
    """
    try:
        content = Content.objects.get(
            id=content_id,
            visibility__in=(Visibility.PUBLIC, Visibility.LIMITED),
            content_type=ContentType.REPLY,
            local=True,
        )
    except Content.DoesNotExist:
        logger.warning("No content found with id %s", content_id)
        return
    entity = make_federable_content(content)
    if not entity:
        logger.warning("send_reply - No entity for %s", content)
    entity.activity_id = activity_fid
    if settings.DEBUG:
        # Don't send in development mode
        return
    recipients = []
    if not content.root_parent.author.is_local:
        recipients.append(
            content.root_parent.author.get_recipient_for_visibility(
                content.visibility))
    if content.visibility == Visibility.PUBLIC:
        recipients.extend(
            _get_remote_participants_for_content(content,
                                                 exclude=content.author.fid,
                                                 include_remote=True), )
        recipients.extend(
            _get_remote_followers(
                content.author,
                content.visibility,
                exclude=content.author.fid,
            ))
    elif content.visibility == Visibility.LIMITED:
        recipients.extend(_get_limited_recipients(content.author.fid, content))
    else:
        return
    if not recipients:
        logger.debug("send_reply - no remote recipients for content: %s",
                     content.id)
        return
    logger.debug("send_reply - sending to recipients: %s", recipients)
    handle_send(entity,
                content.author.federable,
                recipients,
                payload_logger=get_outbound_payload_logger())
Ejemplo n.º 20
0
 def test_calls_handle_create_payload(self, mock_send, mock_create,
                                      diasporapost):
     recipients = [("[email protected]", "diaspora"), ("localhost", None)]
     mock_author = Mock()
     handle_send(diasporapost, mock_author, recipients)
     mock_create.assert_called_once_with(diasporapost,
                                         mock_author,
                                         parent_user=None)
     mock_create.reset_mock()
     handle_send(diasporapost,
                 mock_author,
                 recipients,
                 parent_user="******")
     mock_create.assert_called_once_with(diasporapost,
                                         mock_author,
                                         parent_user="******")
Ejemplo n.º 21
0
def send_content_retraction(content, author_id):
    """Handle sending of retractions.

    Currently only for public content.
    """
    if not content.visibility == Visibility.PUBLIC or not content.is_local:
        return
    author = Profile.objects.get(id=author_id)
    entity = make_federable_retraction(content, author)
    if entity:
        if settings.DEBUG:
            # Don't send in development mode
            return
        recipients = [
            (settings.SOCIALHOME_RELAY_DOMAIN, "diaspora"),
        ]
        recipients.extend(_get_remote_followers(author))
        handle_send(entity, author, recipients)
    else:
        logger.warning("No retraction entity for %s", content)
Ejemplo n.º 22
0
def send_content(content_id, recipient_id=None):
    """
    Handle sending a Content object out via the federation layer.
    """
    try:
        content = Content.objects.get(
            id=content_id,
            visibility__in=(Visibility.PUBLIC, Visibility.LIMITED),
            content_type=ContentType.CONTENT,
            local=True,
        )
    except Content.DoesNotExist:
        logger.warning("No local content found with id %s", content_id)
        return
    if recipient_id:
        try:
            recipient = Profile.objects.get(id=recipient_id, user__isnull=True)
        except Profile.DoesNotExist:
            logger.warning("No remote recipient found with id %s",
                           recipient_id)
            return
    else:
        recipient = None
    entity = make_federable_content(content)
    if entity:
        if settings.DEBUG:
            # Don't send in development mode
            return
        if recipient:
            recipients = [
                # TODO fid or handle?
                (recipient.handle, recipient.key, recipient.guid),
            ]
        else:
            recipients = [settings.SOCIALHOME_RELAY_ID]
            recipients.extend(_get_remote_followers(content.author))

        logger.debug("send_content - sending to recipients: %s", recipients)
        handle_send(entity, content.author.federable, recipients)
    else:
        logger.warning("send_content - No entity for %s", content)
Ejemplo n.º 23
0
    def test_survives_sending_share_if_diaspora_payload_cannot_be_created(
            self, mock_send, share):
        key = get_dummy_private_key()
        share.target_handle = None  # Ensure diaspora payload fails
        recipients = [{
            "endpoint": "https://example.com/receive/public",
            "public": True,
            "protocol": "diaspora",
            "fid": "",
        }, {
            "endpoint": "https://example.tld/receive/public",
            "public": True,
            "protocol": "diaspora",
            "fid": "",
        }, {
            "endpoint": "https://example.net/inbox",
            "fid": "https://example.net/foobar",
            "public": True,
            "protocol": "activitypub",
        }]
        author = UserType(
            private_key=key,
            id="*****@*****.**",
            handle="*****@*****.**",
        )
        handle_send(share, author, recipients)

        # Ensure first call is a public activitypub payload
        args, kwargs = mock_send.call_args_list[0]
        assert args[0] == "https://example.net/inbox"
        assert kwargs['headers'] == {
            'Content-Type':
            'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
        }
        assert encode_if_text(
            "https://www.w3.org/ns/activitystreams#Public") in args[1]

        # Should only be one call
        assert mock_send.call_count == 1
Ejemplo n.º 24
0
def send_reply(content_id):
    """
    Handle sending a Content object that is a reply out via the federation layer.
    """
    try:
        content = Content.objects.get(
            id=content_id,
            visibility__in=(Visibility.PUBLIC, Visibility.LIMITED),
            content_type=ContentType.REPLY,
            local=True,
        )
    except Content.DoesNotExist:
        logger.warning("No content found with id %s", content_id)
        return
    entity = make_federable_content(content)
    if not entity:
        logger.warning("send_reply - No entity for %s", content)
    if settings.DEBUG:
        # Don't send in development mode
        return
    # Send directly (remote parent) or as a relayable (local parent)
    if content.parent.local:
        forward_entity(entity, content.parent.id)
    else:
        # We only need to send to the original author
        parent_author = content.parent.author
        if content.visibility == Visibility.PUBLIC:
            recipients = [
                # TODO fid or handle?
                parent_author.handle,
            ]
        else:
            recipients = [
                # TODO fid or handle?
                (parent_author.handle, parent_author.key, parent_author.guid),
            ]
        logger.debug("send_reply - sending to recipients: %s", recipients)
        handle_send(entity, content.author.federable, recipients)
Ejemplo n.º 25
0
    def test_calls_handle_create_payload(self, mock_send, profile):
        key = get_dummy_private_key()
        recipients = [
            {
                "endpoint": "https://127.0.0.1/receive/users/1234",
                "public_key": key.publickey(),
                "public": False,
                "protocol": "diaspora",
                "fid": "",
            },
            {
                "endpoint": "https://example.com/receive/public",
                "public": True,
                "protocol": "diaspora",
                "fid": "",
            },
            {
                "endpoint": "https://example.net/receive/public",
                "public": True,
                "protocol": "diaspora",
                "fid": "",
            },
            # Same twice to ensure one delivery only per unique
            {
                "endpoint": "https://example.net/receive/public",
                "public": True,
                "protocol": "diaspora",
                "fid": "",
            },
            {
                "endpoint": "https://example.net/foobar/inbox",
                "fid": "https://example.net/foobar",
                "public": False,
                "protocol": "activitypub",
            },
            {
                "endpoint": "https://example.net/inbox",
                "fid": "https://example.net/foobar",
                "public": True,
                "protocol": "activitypub",
            }
        ]
        author = UserType(
            private_key=key,
            id="*****@*****.**",
            handle="*****@*****.**",
        )
        handle_send(profile, author, recipients)

        # Ensure first call is a private diaspora payload
        args, kwargs = mock_send.call_args_list[0]
        assert args[0] == "https://127.0.0.1/receive/users/1234"
        assert "aes_key" in args[1]
        assert "encrypted_magic_envelope" in args[1]
        assert kwargs['headers'] == {'Content-Type': 'application/json'}

        # Ensure second call is a private activitypub payload
        args, kwargs = mock_send.call_args_list[1]
        assert args[0] == "https://example.net/foobar/inbox"
        assert kwargs['headers'] == {
            'Content-Type':
            'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
        }
        assert encode_if_text(
            "https://www.w3.org/ns/activitystreams#Public") not in args[1]

        # Ensure third call is a public activitypub payload
        args, kwargs = mock_send.call_args_list[2]
        assert args[0] == "https://example.net/inbox"
        assert kwargs['headers'] == {
            'Content-Type':
            'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
        }
        assert encode_if_text(
            "https://www.w3.org/ns/activitystreams#Public") in args[1]

        # Ensure diaspora public payloads and recipients, one per unique host
        args3, kwargs3 = mock_send.call_args_list[3]
        args4, kwargs4 = mock_send.call_args_list[4]
        public_endpoints = {args3[0], args4[0]}
        assert public_endpoints == {
            "https://example.net/receive/public",
            "https://example.com/receive/public",
        }
        assert args3[1].startswith("<me:env xmlns:me=")
        assert args4[1].startswith("<me:env xmlns:me=")
        assert kwargs3['headers'] == {
            'Content-Type': 'application/magic-envelope+xml'
        }
        assert kwargs4['headers'] == {
            'Content-Type': 'application/magic-envelope+xml'
        }

        with pytest.raises(IndexError):
            # noinspection PyStatementEffect
            mock_send.call_args_list[5]