def test_visibility_is_not_added_to_public_content(self): entity = entities.PostFactory(public=True) entity._receivers = [ UserType(id=self.receiving_profile.fid, receiver_variant=ReceiverVariant.ACTOR) ] process_entity_post(entity, ProfileFactory()) content = Content.objects.get(fid=entity.id) self.assertEqual(content.limited_visibilities.count(), 0)
def test_message_to_objects_receivers_are_saved__single_receiver(self): # noinspection PyTypeChecker entities = message_to_objects(DIASPORA_POST_SIMPLE, "*****@*****.**", user=Mock(id="*****@*****.**")) entity = entities[0] assert entity._receivers == [ UserType(id="*****@*****.**", receiver_variant=ReceiverVariant.ACTOR) ]
def test_message_to_objects_receivers_are_saved(self): # noinspection PyTypeChecker entities = message_to_objects( ACTIVITYPUB_POST, "https://diaspodon.fr/users/jaywink", ) entity = entities[0] assert set(entity._receivers) == { UserType( id='https://diaspodon.fr/users/jaywink', receiver_variant=ReceiverVariant.FOLLOWERS, ), UserType( id= 'https://dev.jasonrobinson.me/p/d4574854-a5d7-42be-bfac-f70c16fcaa97/', receiver_variant=ReceiverVariant.ACTOR, ) }
def test_visibility__added_to_receiving_profile(self): self.comment._receivers = [ UserType(id=self.receiving_profile.fid, receiver_variant=ReceiverVariant.ACTOR) ] process_entity_comment(self.comment, ProfileFactory()) content = Content.objects.get(fid=self.comment.id, parent=self.content) self.assertEqual( set(content.limited_visibilities.all()), {self.receiving_profile}, )
def test_build_send_uses_outbound_doc(self, mock_me): protocol = self.init_protocol() outbound_doc = etree.fromstring("<xml>foo</xml>") entity = Mock(outbound_doc=outbound_doc) from_user = UserType( id="*****@*****.**", private_key=get_dummy_private_key(), handle="*****@*****.**", ) protocol.build_send(entity, from_user) mock_me.assert_called_once_with( b"<xml>foo</xml>", private_key=from_user.private_key, author_handle="*****@*****.**", )
def test_visibility_is_added_to_receiving_profile(self): entity = entities.PostFactory() entity._receivers = [ UserType(id=self.receiving_profile.fid, receiver_variant=ReceiverVariant.ACTOR) ] process_entity_post(entity, ProfileFactory()) content = Content.objects.get(fid=entity.id) self.assertEqual( set(content.limited_visibilities.all()), {self.receiving_profile}, )
def test_visibility__added_to_receiving_followers(self): self.comment.actor_id = self.profile.fid self.comment._receivers = [ UserType(id=self.comment.actor_id, receiver_variant=ReceiverVariant.FOLLOWERS) ] process_entity_comment(self.comment, self.profile) content = Content.objects.get(fid=self.comment.id) self.assertEqual( set(content.limited_visibilities.all()), {self.local_user.profile, self.local_user2.profile}, )
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")
def test_visibility_is_added_to_receiving_followers(self): entity = entities.PostFactory(actor_id=self.profile.fid) entity._receivers = [ UserType(id=entity.actor_id, receiver_variant=ReceiverVariant.FOLLOWERS) ] process_entity_post(entity, self.profile) content = Content.objects.get(fid=entity.id) self.assertEqual( set(content.limited_visibilities.all()), {self.local_user.profile, self.local_user2.profile}, )
def test_message_to_objects_receivers_are_saved__followers_receiver(self): # noinspection PyTypeChecker entities = message_to_objects( DIASPORA_POST_SIMPLE, "*****@*****.**", ) entity = entities[0] assert entity._receivers == [ UserType( id="*****@*****.**", receiver_variant=ReceiverVariant.FOLLOWERS, ) ]
def extract_receiver(payload: Dict, receiver: str) -> Optional[UserType]: """ Transform a single receiver ID to a UserType. """ actor = payload.get("actor") or payload.get("attributedTo") or "" if receiver == NAMESPACE_PUBLIC: # Ignore since we already store "public" as a boolean on the entity return # Check for this being a list reference to followers of an actor? # TODO: terrible hack! the way some platforms deliver to sharedInbox using just # the followers collection as a target is annoying to us since we would have to # store the followers collection references on application side, which we don't # want to do since it would make application development another step more complex. # So for now we're going to do a terrible assumption that # 1) if "followers" in ID and # 2) if ID starts with actor ID # then; assume this is the followers collection of said actor ID. # When we have a caching system, just fetch each receiver and check what it is. # Without caching this would be too expensive to do. elif receiver.find("followers") > -1 and receiver.startswith(actor): return UserType(id=actor, receiver_variant=ReceiverVariant.FOLLOWERS) # Assume actor ID return UserType(id=receiver, receiver_variant=ReceiverVariant.ACTOR)
def test_visibility_is_not_added_if_public_parent_content(self): comment = base.Comment( id="https://example.com/comment2", target_id=self.public_content.fid, raw_content="foobar", actor_id="https://example.com/profile2", ) comment._receivers = [ UserType(id=self.receiving_profile.fid, receiver_variant=ReceiverVariant.ACTOR) ] process_entity_comment(comment, ProfileFactory()) content = Content.objects.get(fid=comment.id, parent=self.public_content) self.assertEqual(content.limited_visibilities.count(), 0)
def test_build_send_does_right_calls(self, mock_me): mock_render = Mock(return_value="rendered") mock_me_instance = Mock(render=mock_render) mock_me.return_value = mock_me_instance protocol = Protocol() entity = DiasporaPost() private_key = get_dummy_private_key() outbound_entity = get_outbound_entity(entity, private_key) data = protocol.build_send(outbound_entity, from_user=UserType( private_key=private_key, id="johnny@localhost", handle="johnny@localhost", )) mock_me.assert_called_once_with( etree.tostring(entity.to_xml()), private_key=private_key, author_handle="johnny@localhost", ) mock_render.assert_called_once_with() assert data == "rendered"
def test_visibility__added_if_public_parent_content_if_visibility_on_comment__non_public_comment( self): comment = ActivitypubComment( id="https://example.com/comment2", target_id=self.public_content.fid, raw_content="foobar", actor_id="https://example.com/profile2", public=False, ) comment._receivers = [ UserType(id=self.receiving_profile.fid, receiver_variant=ReceiverVariant.ACTOR) ] process_entity_comment(comment, ProfileFactory()) content = Content.objects.get(fid=comment.id, parent=self.public_content) self.assertEqual( set(content.limited_visibilities.all()), {self.receiving_profile}, )
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
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]
def federable(self): return UserType( id=self.fid or self.handle, private_key=self.rsa_private_key, handle=self.handle, )
def usertype(): return UserType( id="https://localhost/profile", private_key=get_dummy_private_key(), )
def element_to_objects( element: etree.ElementTree, sender: str, sender_key_fetcher: Callable[[str], str] = None, user: UserType = None, ) -> List: """Transform an Element to a list of entities recursively. Possible child entities are added to each entity ``_children`` list. Optional parameter ``sender_key_fetcher`` can be a function to fetch sender public key. If not given, key will always be fetched over the network. The function should take sender as the only parameter. """ entities = [] cls = MAPPINGS.get(element.tag) if not cls: return [] attrs = xml_children_as_dict(element) transformed = transform_attributes(attrs, cls) if hasattr(cls, "fill_extra_attributes"): transformed = cls.fill_extra_attributes(transformed) entity = cls(**transformed) # Add protocol name entity._source_protocol = "diaspora" # Save element object to entity for possible later use entity._source_object = etree.tostring(element) # Save receivers on the entity if user: # Single receiver entity._receivers = [UserType(id=user.id, receiver_variant=ReceiverVariant.ACTOR)] else: # Followers entity._receivers = [UserType(id=sender, receiver_variant=ReceiverVariant.FOLLOWERS)] if issubclass(cls, DiasporaRelayableMixin): # If relayable, fetch sender key for validation entity._xml_tags = get_element_child_info(element, "tag") if sender_key_fetcher: entity._sender_key = sender_key_fetcher(entity.actor_id) else: profile = retrieve_and_parse_profile(entity.handle) if profile: entity._sender_key = profile.public_key else: # If not relayable, ensure handles match if not check_sender_and_entity_handle_match(sender, entity.handle): return [] try: entity.validate() except ValueError as ex: logger.error("Failed to validate entity %s: %s", entity, ex, extra={ "attrs": attrs, "transformed": transformed, }) return [] # Extract mentions if hasattr(entity, "extract_mentions"): entity.extract_mentions() # Do child elements for child in element: # noinspection PyProtectedMember entity._children.extend(element_to_objects(child, sender, user=user)) # Add to entities list entities.append(entity) return entities
def test_get_json_payload_magic_envelope(self, mock_decrypt): protocol = Protocol() protocol.user = UserType(id="foobar", private_key=get_dummy_private_key()) protocol.get_json_payload_magic_envelope("payload") mock_decrypt.assert_called_once_with(payload="payload", private_key=get_dummy_private_key())