Refactor processing Diaspora payload xml

New protocol version is dropping the xml/post wrapper elements. Support parsing these payloads plus legacy ones.

Refs: #60
merge-requests/130/head
Jason Robinson 2017-05-06 20:19:40 +03:00
rodzic 7e0651bb35
commit b27ecc5223
2 zmienionych plików z 77 dodań i 67 usunięć

Wyświetl plik

@ -21,19 +21,24 @@ MAPPINGS = {
"retraction": DiasporaRetraction,
}
BOOLEAN_KEYS = [
TAGS = [
# Order is important. Any top level tags should be before possibly child tags
"status_message", "comment", "like", "request", "profile", "retraction", "photo",
]
BOOLEAN_KEYS = (
"public",
"nsfw",
]
)
DATETIME_KEYS = [
DATETIME_KEYS = (
"created_at",
]
)
INTEGER_KEYS = [
INTEGER_KEYS = (
"height",
"width",
]
)
def xml_children_as_dict(node):
"""Turn the children of node <xml> into a dict, keyed by tag name.
@ -43,8 +48,8 @@ def xml_children_as_dict(node):
return dict((e.tag, e.text) for e in node)
def element_to_objects(tree, sender_key_fetcher=None):
"""Transform an Element tree to a list of entities recursively.
def element_to_objects(element, sender_key_fetcher=None):
"""Transform an Element to a list of entities recursively.
Possible child entities are added to each entity `_children` list.
@ -54,45 +59,45 @@ def element_to_objects(tree, sender_key_fetcher=None):
:returns: list of entities
"""
entities = []
for element in tree:
cls = MAPPINGS.get(element.tag, None)
if not cls:
continue
cls = MAPPINGS.get(element.tag, None)
if not cls:
return []
attrs = xml_children_as_dict(element)
transformed = transform_attributes(attrs)
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 = element
# If relayable, fetch sender key for validation
if issubclass(cls, DiasporaRelayableMixin):
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
try:
entity.validate()
except ValueError as ex:
logger.error("Failed to validate entity %s: %s", entity, ex, extra={
"attrs": attrs,
"transformed": transformed,
})
continue
# Do child elements
entity._children = element_to_objects(element)
# 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)
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 = element
# If relayable, fetch sender key for validation
if issubclass(cls, DiasporaRelayableMixin):
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
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 = element_to_objects(child)
# 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
@ -106,11 +111,15 @@ def message_to_objects(message, sender_key_fetcher=None):
:returns: list of entities
"""
doc = etree.fromstring(message)
if doc[0].tag == "post":
# Skip the top <post> element if it exists
doc = doc[0]
entities = element_to_objects(doc, sender_key_fetcher)
return entities
# Future Diaspora protocol version contains the element at top level
if doc.tag in TAGS:
return element_to_objects(doc, sender_key_fetcher)
# Legacy Diaspora protocol wraps the element in <XML><post></post></XML>, so find the right element
for tag in TAGS:
element = doc.find(".//%s" % tag)
if element is not None:
return element_to_objects(element, sender_key_fetcher)
return []
def transform_attributes(attrs, cls):

Wyświetl plik

@ -15,7 +15,7 @@ from federation.tests.fixtures.keys import get_dummy_private_key
from federation.tests.fixtures.payloads import (
DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT, DIASPORA_POST_LIKE,
DIASPORA_REQUEST, DIASPORA_PROFILE, DIASPORA_POST_INVALID, DIASPORA_RETRACTION,
DIASPORA_POST_WITH_PHOTOS, DIASPORA_POST_LEGACY_TIMESTAMP)
DIASPORA_POST_WITH_PHOTOS, DIASPORA_POST_LEGACY_TIMESTAMP, DIASPORA_POST_LEGACY)
def mock_fill(attributes):
@ -23,7 +23,7 @@ def mock_fill(attributes):
return attributes
class TestDiasporaEntityMappersReceive(object):
class TestDiasporaEntityMappersReceive():
def test_message_to_objects_simple_post(self):
entities = message_to_objects(DIASPORA_POST_SIMPLE)
assert len(entities) == 1
@ -37,6 +37,20 @@ class TestDiasporaEntityMappersReceive(object):
assert post.created_at == datetime(2011, 7, 20, 1, 36, 7)
assert post.provider_display_name == "Socialhome"
def test_message_to_objects_post_legacy(self):
# This is the previous XML schema used before renewal of protocol
entities = message_to_objects(DIASPORA_POST_LEGACY)
assert len(entities) == 1
post = entities[0]
assert isinstance(post, DiasporaPost)
assert isinstance(post, Post)
assert post.raw_content == "((status message))"
assert post.guid == "((guidguidguidguidguidguidguid))"
assert post.handle == "alice@alice.diaspora.example.org"
assert post.public == False
assert post.created_at == datetime(2011, 7, 20, 1, 36, 7)
assert post.provider_display_name == "Socialhome"
def test_message_to_objects_legact_timestamp(self):
entities = message_to_objects(DIASPORA_POST_LEGACY_TIMESTAMP)
post = entities[0]
@ -60,19 +74,6 @@ class TestDiasporaEntityMappersReceive(object):
assert photo.handle == "alice@alice.diaspora.example.org"
assert photo.public == False
assert photo.created_at == datetime(2011, 7, 20, 1, 36, 7)
photo = post._children[1]
assert isinstance(photo, Image)
assert photo.remote_path == "https://alice.diaspora.example.org/uploads/images/"
assert photo.remote_name == "12345.jpg"
assert photo.raw_content == "foobar"
assert photo.linked_type == "Post"
assert photo.linked_guid == "((guidguidguidguidguidguidguid))"
assert photo.height == 120
assert photo.width == 120
assert photo.guid == "((guidguidguidguidguidguidguig))"
assert photo.handle == "alice@alice.diaspora.example.org"
assert photo.public == False
assert photo.created_at == datetime(2011, 7, 20, 1, 36, 7)
@patch("federation.entities.diaspora.mappers.DiasporaComment._validate_signatures")
def test_message_to_objects_comment(self, mock_validate):
@ -171,7 +172,7 @@ class TestDiasporaEntityMappersReceive(object):
mock_retrieve.assert_called_once_with("alice@alice.diaspora.example.org")
class TestGetOutboundEntity(object):
class TestGetOutboundEntity():
def test_already_fine_entities_are_returned_as_is(self):
dummy_key = get_dummy_private_key()
entity = DiasporaPost()