kopia lustrzana https://gitlab.com/jaywink/federation
Refactor processing Diaspora payload xml
New protocol version is dropping the xml/post wrapper elements. Support parsing these payloads plus legacy ones. Refs: #60merge-requests/130/head
rodzic
7e0651bb35
commit
b27ecc5223
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
|
Ładowanie…
Reference in New Issue