kopia lustrzana https://gitlab.com/jaywink/federation
Refactor: move diaspora XML generators to entities themselves
This change is backwards incompatible. Any implementations wishing to create a valid Post for example for diaspora shouls now use DiasporaPost.merge-requests/130/head
rodzic
62380a5d77
commit
a2920444ca
|
@ -1,10 +1,36 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from ..base import Comment
|
from lxml import etree
|
||||||
|
|
||||||
|
from federation.entities.base import Comment, Post
|
||||||
|
from federation.entities.diaspora.utils import format_dt, struct_to_xml
|
||||||
|
|
||||||
|
|
||||||
class DiasporaComment(Comment):
|
class DiasporaComment(Comment):
|
||||||
"""Diaspora comments additionally have an author_signature."""
|
"""Diaspora comments additionally have an author_signature."""
|
||||||
@property
|
author_signature = ""
|
||||||
def author_signature(self):
|
|
||||||
#TODO: implement at later stage when outbound payloads are to be used
|
def to_xml(self):
|
||||||
return ""
|
element = etree.Element("comment")
|
||||||
|
struct_to_xml(element, [
|
||||||
|
{'guid': self.guid},
|
||||||
|
{'parent_guid': self.target_guid},
|
||||||
|
{'author_signature': self.author_signature},
|
||||||
|
{'text': self.raw_content},
|
||||||
|
{'diaspora_handle': self.handle},
|
||||||
|
])
|
||||||
|
return element
|
||||||
|
|
||||||
|
|
||||||
|
class DiasporaPost(Post):
|
||||||
|
"""Diaspora post, ie status message."""
|
||||||
|
def to_xml(self):
|
||||||
|
"""Convert to XML message."""
|
||||||
|
element = etree.Element("status_message")
|
||||||
|
struct_to_xml(element, [
|
||||||
|
{'raw_message': self.raw_content},
|
||||||
|
{'guid': self.guid},
|
||||||
|
{'diaspora_handle': self.handle},
|
||||||
|
{'public': 'true' if self.public else 'false'},
|
||||||
|
{'created_at': format_dt(self.created_at)}
|
||||||
|
])
|
||||||
|
return element
|
||||||
|
|
|
@ -1,67 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from dateutil.tz import tzlocal, tzutc
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_timezone(dt, tz=None):
|
|
||||||
"""
|
|
||||||
Make sure the datetime <dt> has a timezone set, using timezone <tz> if it
|
|
||||||
doesn't. <tz> defaults to the local timezone.
|
|
||||||
"""
|
|
||||||
if dt.tzinfo is None:
|
|
||||||
return dt.replace(tzinfo=tz or tzlocal())
|
|
||||||
else:
|
|
||||||
return dt
|
|
||||||
|
|
||||||
|
|
||||||
class EntityConverter(object):
|
|
||||||
|
|
||||||
def __init__(self, entity):
|
|
||||||
self.entity = entity
|
|
||||||
self.entity_type = entity.__class__.__name__.lower()
|
|
||||||
|
|
||||||
def struct_to_xml(self, node, struct):
|
|
||||||
"""
|
|
||||||
Turn a list of dicts into XML nodes with tag names taken from the dict
|
|
||||||
keys and element text taken from dict values. This is a list of dicts
|
|
||||||
so that the XML nodes can be ordered in the XML output.
|
|
||||||
"""
|
|
||||||
for obj in struct:
|
|
||||||
for k, v in obj.items():
|
|
||||||
etree.SubElement(node, k).text = v
|
|
||||||
|
|
||||||
def convert_to_xml(self):
|
|
||||||
#TODO: move these to entities themselves as `to_xml` methods
|
|
||||||
if hasattr(self, "%s_to_xml" % self.entity_type):
|
|
||||||
method_name = "%s_to_xml" % self.entity_type
|
|
||||||
return getattr(self, method_name)()
|
|
||||||
|
|
||||||
def format_dt(cls, dt):
|
|
||||||
"""
|
|
||||||
Format a datetime in the way that D* nodes expect.
|
|
||||||
"""
|
|
||||||
return ensure_timezone(dt).astimezone(tzutc()).strftime(
|
|
||||||
'%Y-%m-%d %H:%M:%S %Z'
|
|
||||||
)
|
|
||||||
|
|
||||||
def post_to_xml(self):
|
|
||||||
req = etree.Element("status_message")
|
|
||||||
self.struct_to_xml(req, [
|
|
||||||
{'raw_message': self.entity.raw_content},
|
|
||||||
{'guid': self.entity.guid},
|
|
||||||
{'diaspora_handle': self.entity.handle},
|
|
||||||
{'public': 'true' if self.entity.public else 'false'},
|
|
||||||
{'created_at': self.format_dt(self.entity.created_at)}
|
|
||||||
])
|
|
||||||
return req
|
|
||||||
|
|
||||||
def diasporacomment_to_xml(self):
|
|
||||||
req = etree.Element("comment")
|
|
||||||
self.struct_to_xml(req, [
|
|
||||||
{'guid': self.entity.guid},
|
|
||||||
{'parent_guid': self.entity.target_guid},
|
|
||||||
{'author_signature': self.entity.author_signature},
|
|
||||||
{'text': self.entity.raw_content},
|
|
||||||
{'diaspora_handle': self.entity.handle},
|
|
||||||
])
|
|
||||||
return req
|
|
|
@ -3,13 +3,14 @@ from datetime import datetime
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from federation.entities.base import Post, Image, Comment
|
from federation.entities.base import Image
|
||||||
|
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment
|
||||||
|
|
||||||
|
|
||||||
MAPPINGS = {
|
MAPPINGS = {
|
||||||
"status_message": Post,
|
"status_message": DiasporaPost,
|
||||||
"photo": Image,
|
"photo": Image,
|
||||||
"comment": Comment,
|
"comment": DiasporaComment,
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOLEAN_KEYS = [
|
BOOLEAN_KEYS = [
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from dateutil.tz import tzlocal, tzutc
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_timezone(dt, tz=None):
|
||||||
|
"""
|
||||||
|
Make sure the datetime <dt> has a timezone set, using timezone <tz> if it
|
||||||
|
doesn't. <tz> defaults to the local timezone.
|
||||||
|
"""
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
return dt.replace(tzinfo=tz or tzlocal())
|
||||||
|
else:
|
||||||
|
return dt
|
||||||
|
|
||||||
|
|
||||||
|
def format_dt(dt):
|
||||||
|
"""
|
||||||
|
Format a datetime in the way that D* nodes expect.
|
||||||
|
"""
|
||||||
|
return ensure_timezone(dt).astimezone(tzutc()).strftime(
|
||||||
|
'%Y-%m-%d %H:%M:%S %Z'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def struct_to_xml(node, struct):
|
||||||
|
"""
|
||||||
|
Turn a list of dicts into XML nodes with tag names taken from the dict
|
||||||
|
keys and element text taken from dict values. This is a list of dicts
|
||||||
|
so that the XML nodes can be ordered in the XML output.
|
||||||
|
"""
|
||||||
|
for obj in struct:
|
||||||
|
for k, v in obj.items():
|
||||||
|
etree.SubElement(node, k).text = v
|
|
@ -10,7 +10,6 @@ from Crypto.Random import get_random_bytes
|
||||||
from Crypto.Signature import PKCS1_v1_5 as PKCSSign
|
from Crypto.Signature import PKCS1_v1_5 as PKCSSign
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from federation.entities.diaspora.generators import EntityConverter
|
|
||||||
from federation.exceptions import EncryptedMessageError, NoHeaderInMessageError, NoSenderKeyFoundError
|
from federation.exceptions import EncryptedMessageError, NoHeaderInMessageError, NoSenderKeyFoundError
|
||||||
from federation.protocols.base import BaseProtocol
|
from federation.protocols.base import BaseProtocol
|
||||||
|
|
||||||
|
@ -164,8 +163,7 @@ class Protocol(BaseProtocol):
|
||||||
|
|
||||||
def build_send(self, from_user, to_user, entity, *args, **kwargs):
|
def build_send(self, from_user, to_user, entity, *args, **kwargs):
|
||||||
"""Build POST data for sending out to remotes."""
|
"""Build POST data for sending out to remotes."""
|
||||||
converter = EntityConverter(entity)
|
xml = entity.to_xml()
|
||||||
xml = converter.convert_to_xml()
|
|
||||||
self.init_message(xml, from_user.handle, from_user.private_key)
|
self.init_message(xml, from_user.handle, from_user.private_key)
|
||||||
xml = quote_plus(
|
xml = quote_plus(
|
||||||
self.create_salmon_envelope(to_user.key))
|
self.create_salmon_envelope(to_user.key))
|
||||||
|
|
|
@ -1,28 +1,13 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
from federation.entities.base import Post
|
from federation.entities.diaspora.entities import DiasporaComment, DiasporaPost
|
||||||
from federation.entities.diaspora.entities import DiasporaComment
|
|
||||||
from federation.entities.diaspora.generators import EntityConverter
|
|
||||||
|
|
||||||
|
|
||||||
class TestEntityConverterCallsToXML(object):
|
class TestEntitiesConvertToXML(object):
|
||||||
|
def test_post_to_xml(self):
|
||||||
def test_entity_converter_call_to_xml(self):
|
entity = DiasporaPost(raw_content="raw_content", guid="guid", handle="handle", public=True)
|
||||||
entity = Post()
|
result = entity.to_xml()
|
||||||
|
|
||||||
with patch.object(EntityConverter, "post_to_xml", return_value="foo") as mock_to_xml:
|
|
||||||
entity_converter = EntityConverter(entity=entity)
|
|
||||||
result = entity_converter.convert_to_xml()
|
|
||||||
assert result == "foo"
|
|
||||||
assert mock_to_xml.called
|
|
||||||
|
|
||||||
def test_entity_converter_converts_a_post(self):
|
|
||||||
entity = Post(raw_content="raw_content", guid="guid", handle="handle", public=True)
|
|
||||||
entity_converter = EntityConverter(entity)
|
|
||||||
result = entity_converter.convert_to_xml()
|
|
||||||
assert result.tag == "status_message"
|
assert result.tag == "status_message"
|
||||||
assert len(result.find("created_at").text) > 0
|
assert len(result.find("created_at").text) > 0
|
||||||
result.find("created_at").text = "" # timestamp makes testing painful
|
result.find("created_at").text = "" # timestamp makes testing painful
|
||||||
|
@ -31,10 +16,9 @@ class TestEntityConverterCallsToXML(object):
|
||||||
b"</created_at></status_message>"
|
b"</created_at></status_message>"
|
||||||
assert etree.tostring(result) == converted
|
assert etree.tostring(result) == converted
|
||||||
|
|
||||||
def test_entity_converter_converts_a_comment(self):
|
def test_comment_to_xml(self):
|
||||||
entity = DiasporaComment(raw_content="raw_content", guid="guid", target_guid="target_guid", handle="handle")
|
entity = DiasporaComment(raw_content="raw_content", guid="guid", target_guid="target_guid", handle="handle")
|
||||||
entity_converter = EntityConverter(entity)
|
result = entity.to_xml()
|
||||||
result = entity_converter.convert_to_xml()
|
|
||||||
assert result.tag == "comment"
|
assert result.tag == "comment"
|
||||||
converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \
|
converted = b"<comment><guid>guid</guid><parent_guid>target_guid</parent_guid>" \
|
||||||
b"<author_signature></author_signature><text>raw_content</text>" \
|
b"<author_signature></author_signature><text>raw_content</text>" \
|
|
@ -1,7 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from federation.entities.base import Post, Comment
|
from federation.entities.base import Comment, Post
|
||||||
|
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment
|
||||||
from federation.entities.diaspora.mappers import message_to_objects
|
from federation.entities.diaspora.mappers import message_to_objects
|
||||||
from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT
|
from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT
|
||||||
|
|
||||||
|
@ -12,6 +13,7 @@ class TestDiasporaEntityMappersReceive(object):
|
||||||
entities = message_to_objects(DIASPORA_POST_SIMPLE)
|
entities = message_to_objects(DIASPORA_POST_SIMPLE)
|
||||||
assert len(entities) == 1
|
assert len(entities) == 1
|
||||||
post = entities[0]
|
post = entities[0]
|
||||||
|
assert isinstance(post, DiasporaPost)
|
||||||
assert isinstance(post, Post)
|
assert isinstance(post, Post)
|
||||||
assert post.raw_content == "((status message))"
|
assert post.raw_content == "((status message))"
|
||||||
assert post.guid == "((guid))"
|
assert post.guid == "((guid))"
|
||||||
|
@ -23,6 +25,7 @@ class TestDiasporaEntityMappersReceive(object):
|
||||||
entities = message_to_objects(DIASPORA_POST_COMMENT)
|
entities = message_to_objects(DIASPORA_POST_COMMENT)
|
||||||
assert len(entities) == 1
|
assert len(entities) == 1
|
||||||
comment = entities[0]
|
comment = entities[0]
|
||||||
|
assert isinstance(comment, DiasporaComment)
|
||||||
assert isinstance(comment, Comment)
|
assert isinstance(comment, Comment)
|
||||||
assert comment.target_guid == "((parent_guid))"
|
assert comment.target_guid == "((parent_guid))"
|
||||||
assert comment.guid == "((guid))"
|
assert comment.guid == "((guid))"
|
||||||
|
|
|
@ -4,7 +4,7 @@ from Crypto.PublicKey import RSA
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from federation.controllers import handle_receive, handle_create_payload
|
from federation.controllers import handle_receive, handle_create_payload
|
||||||
from federation.entities.base import Post
|
from federation.entities.diaspora.entities import DiasporaPost
|
||||||
from federation.exceptions import NoSuitableProtocolFoundError
|
from federation.exceptions import NoSuitableProtocolFoundError
|
||||||
from federation.protocols.diaspora.protocol import Protocol
|
from federation.protocols.diaspora.protocol import Protocol
|
||||||
from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD
|
from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD
|
||||||
|
@ -35,7 +35,7 @@ class TestHandleCreatePayloadBuildsAPayload(object):
|
||||||
def test_handle_create_payload_builds_an_xml(self):
|
def test_handle_create_payload_builds_an_xml(self):
|
||||||
from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld")
|
from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld")
|
||||||
to_user = Mock(key=RSA.generate(2048).publickey())
|
to_user = Mock(key=RSA.generate(2048).publickey())
|
||||||
entity = Post()
|
entity = DiasporaPost()
|
||||||
data = handle_create_payload(from_user, to_user, entity)
|
data = handle_create_payload(from_user, to_user, entity)
|
||||||
assert len(data) > 0
|
assert len(data) > 0
|
||||||
parts = data.split("=")
|
parts = data.split("=")
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
[pytest]
|
||||||
|
testpaths = federation
|
Ładowanie…
Reference in New Issue