def fill_extra_attributes(attributes): """Diaspora Profile XML message contains no GUID. We need the guid. Fetch it.""" if not attributes.get("handle"): raise ValueError("Can't fill GUID for profile creation since there is no handle! Attrs: %s" % attributes) profile = retrieve_and_parse_profile(attributes.get("handle")) attributes["guid"] = profile.guid return attributes
def test_parse_profile_from_hcard_called(self, mock_retrieve, mock_parse): hcard = generate_hcard( "diaspora", hostname="https://hostname", fullname="fullname", firstname="firstname", lastname="lastname", photo300="photo300", photo100="photo100", photo50="photo50", searchable="true", guid="guid", public_key="public_key", username="******", ) mock_retrieve.return_value = hcard retrieve_and_parse_profile("foo@bar") mock_parse.assert_called_with(hcard, "foo@bar")
def test_profile_validate_is_called(self, mock_retrieve, mock_parse): hcard = generate_hcard( "diaspora", hostname="https://hostname", fullname="fullname", firstname="firstname", lastname="lastname", photo300="photo300", photo100="photo100", photo50="photo50", searchable="true", guid="guid", public_key="public_key", username="******", ) mock_retrieve.return_value = hcard mock_profile = Mock() mock_parse.return_value = mock_profile retrieve_and_parse_profile("foo@bar") assert mock_profile.validate.called
def test_profile_that_doesnt_validate_returns_none(self, mock_retrieve, mock_parse): hcard = generate_hcard( "diaspora", hostname="https://hostname", fullname="fullname", firstname="firstname", lastname="lastname", photo300="photo300", photo100="photo100", photo50="photo50", searchable="true", guid="guid", public_key="public_key", username="******", ) mock_retrieve.return_value = hcard mock_parse.return_value = Profile(guid="123") profile = retrieve_and_parse_profile("foo@bar") assert profile == None
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 transform_attributes(attrs, cls): """Transform some attribute keys. :param attrs: Properties from the XML :type attrs: dict :param cls: Class of the entity :type cls: class """ transformed = {} for key, value in attrs.items(): if value is None: value = "" if key == "text": transformed["raw_content"] = value elif key == "activitypub_id": transformed["id"] = value elif key == "author": if cls == DiasporaProfile: # Diaspora Profile XML message contains no GUID. We need the guid. Fetch it. profile = retrieve_and_parse_profile(value) transformed['id'] = value transformed["guid"] = profile.guid else: transformed["actor_id"] = value transformed["handle"] = value elif key == 'guid': if cls != DiasporaProfile: transformed["id"] = value transformed["guid"] = value elif key in ("root_author", "recipient"): transformed["target_id"] = value transformed["target_handle"] = value elif key in ("target_guid", "root_guid", "parent_guid"): transformed["target_id"] = value transformed["target_guid"] = value elif key == "thread_parent_guid": transformed["root_target_id"] = value transformed["root_target_guid"] = value elif key in ("first_name", "last_name"): values = [attrs.get('first_name'), attrs.get('last_name')] values = [v for v in values if v] transformed["name"] = " ".join(values) elif key == "image_url": if "image_urls" not in transformed: transformed["image_urls"] = {} transformed["image_urls"]["large"] = value elif key == "image_url_small": if "image_urls" not in transformed: transformed["image_urls"] = {} transformed["image_urls"]["small"] = value elif key == "image_url_medium": if "image_urls" not in transformed: transformed["image_urls"] = {} transformed["image_urls"]["medium"] = value elif key == "tag_string": if value: transformed["tag_list"] = value.replace("#", "").split(" ") elif key == "bio": transformed["raw_content"] = value elif key == "searchable": transformed["public"] = True if value == "true" else False elif key in ["target_type"] and cls == DiasporaRetraction: transformed["entity_type"] = DiasporaRetraction.entity_type_from_remote(value) elif key == "remote_photo_path": transformed["url"] = f"{value}{attrs.get('remote_photo_name')}" elif key == "author_signature": transformed["signature"] = value elif key in BOOLEAN_KEYS: transformed[key] = True if value == "true" else False elif key in DATETIME_KEYS: transformed[key] = datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ") elif key in INTEGER_KEYS: transformed[key] = int(value) else: transformed[key] = value return transformed
def element_to_objects(element, sender, sender_key_fetcher=None, user=None): """Transform an Element to a list of entities recursively. Possible child entities are added to each entity `_children` list. :param tree: Element :param sender: Payload sender handle :param sender_key_fetcher: Function to fetch sender public key. If not given, key will always be fetched over network. The function should take sender handle as the only parameter. :param user: Optional receiving user object. If given, should have a `handle`. :returns: list of entities """ entities = [] cls = MAPPINGS.get(element.tag, None) 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 receiving guid to object if user and hasattr(user, "guid"): entity._receiving_guid = user.guid 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.handle) 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 [] # Do child elements for child in element: entity._children.extend(element_to_objects(child, sender)) # Add to entities list entities.append(entity) if cls == DiasporaRequest: # We support sharing/following separately, so also generate base Relationship for the following part transformed.update({"relationship": "following"}) relationship = Relationship(**transformed) entities.append(relationship) return entities
def test_retrieve_diaspora_hcard_is_called(self, mock_retrieve): retrieve_and_parse_profile("foo@bar") mock_retrieve.assert_called_with("foo@bar")