Convert outbound entities to correct protocol types

When sending an entity, first convert it to the correct entity using the protocol entities. If a suitable entity is not found, raise an error.

Closes #27
merge-requests/130/head
Jason Robinson 2016-07-18 23:26:25 +03:00
rodzic 8b880f89a0
commit bf348e9544
8 zmienionych plików z 128 dodań i 11 usunięć

Wyświetl plik

@ -5,6 +5,7 @@
## Changed ## Changed
- Unlock most of the direct dependencies to a certain version range. Unlock all of test requirements to any version. - Unlock most of the direct dependencies to a certain version range. Unlock all of test requirements to any version.
- Entities passed to `federation.controllers.handle_create_payload` are now converted from the base entity types (Post, Comment, Reaction, etc) to Diaspora entity types (DiasporaPost, DiasporaComment, DiasporaLike, etc). This ensures actual payload generation has the correct methods available (for example `to_xml`) whatever entity is passed in.
### Fixes ### Fixes
- Fix fetching sender handle from Diaspora protocol private messages. As it is not contained in the header, it needs to be read from the message content itself. - Fix fetching sender handle from Diaspora protocol private messages. As it is not contained in the header, it needs to be read from the message content itself.

Wyświetl plik

@ -1,5 +1,6 @@
import importlib import importlib
from federation.entities.diaspora.mappers import get_outbound_entity
from federation.exceptions import NoSuitableProtocolFoundError from federation.exceptions import NoSuitableProtocolFoundError
from federation.protocols.diaspora.protocol import Protocol from federation.protocols.diaspora.protocol import Protocol
@ -51,6 +52,8 @@ def handle_create_payload(from_user, to_user, entity):
`from_user` must have `private_key` and `handle` attributes. `from_user` must have `private_key` and `handle` attributes.
`to_user` must have `key` attribute. `to_user` must have `key` attribute.
""" """
# Just use Diaspora protocol for now
protocol = Protocol() protocol = Protocol()
data = protocol.build_send(from_user=from_user, to_user=to_user, entity=entity) outbound_entity = get_outbound_entity(entity)
data = protocol.build_send(from_user=from_user, to_user=to_user, entity=outbound_entity)
return data return data

Wyświetl plik

@ -2,10 +2,16 @@
from lxml import etree from lxml import etree
from federation.entities.base import Comment, Post, Reaction, Relationship from federation.entities.base import Comment, Post, Reaction, Relationship
from federation.entities.diaspora.utils import format_dt, struct_to_xml from federation.entities.diaspora.utils import format_dt, struct_to_xml, get_base_attributes
class DiasporaComment(Comment): class DiasporaEntityMixin(object):
@classmethod
def from_base(cls, entity):
return cls(**get_base_attributes(entity))
class DiasporaComment(DiasporaEntityMixin, Comment):
"""Diaspora comment.""" """Diaspora comment."""
author_signature = "" author_signature = ""
@ -21,7 +27,7 @@ class DiasporaComment(Comment):
return element return element
class DiasporaPost(Post): class DiasporaPost(DiasporaEntityMixin, Post):
"""Diaspora post, ie status message.""" """Diaspora post, ie status message."""
def to_xml(self): def to_xml(self):
"""Convert to XML message.""" """Convert to XML message."""
@ -36,7 +42,7 @@ class DiasporaPost(Post):
return element return element
class DiasporaLike(Reaction): class DiasporaLike(DiasporaEntityMixin, Reaction):
"""Diaspora like.""" """Diaspora like."""
author_signature = "" author_signature = ""
reaction = "like" reaction = "like"
@ -55,7 +61,7 @@ class DiasporaLike(Reaction):
return element return element
class DiasporaRequest(Relationship): class DiasporaRequest(DiasporaEntityMixin, Relationship):
"""Diaspora relationship request.""" """Diaspora relationship request."""
relationship = "sharing" relationship = "sharing"

Wyświetl plik

@ -3,7 +3,7 @@ from datetime import datetime
from lxml import etree from lxml import etree
from federation.entities.base import Image, Relationship from federation.entities.base import Image, Relationship, Post, Reaction, Comment
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest
MAPPINGS = { MAPPINGS = {
@ -69,3 +69,33 @@ def transform_attributes(attrs):
else: else:
transformed[key] = value transformed[key] = value
return transformed return transformed
def get_outbound_entity(entity):
"""Get the correct outbound entity for this protocol.
We might have to look at entity values to decide the correct outbound entity.
If we cannot find one, we should raise as conversion cannot be guaranteed to the given protocol.
Args:
entity - any of the base entity types from federation.entities.base
Returns:
An instance of the correct protocol specific entity.
"""
cls = entity.__class__
if cls in [DiasporaPost, DiasporaRequest, DiasporaComment, DiasporaLike]:
# Already fine
return entity
elif cls == Post:
return DiasporaPost.from_base(entity)
elif cls == Comment:
return DiasporaComment.from_base(entity)
elif cls == Reaction:
if entity.reaction == "like":
return DiasporaLike.from_base(entity)
elif cls == Relationship:
if entity.relationship in ["sharing", "following"]:
# Unfortunately we must send out in both cases since in Diaspora they are the same thing
return DiasporaRequest.from_base(entity)
raise ValueError("Don't know how to convert this base entity to Diaspora protocol entities.")

Wyświetl plik

@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import inspect
from dateutil.tz import tzlocal, tzutc from dateutil.tz import tzlocal, tzutc
from lxml import etree from lxml import etree
@ -32,3 +34,17 @@ def struct_to_xml(node, struct):
for obj in struct: for obj in struct:
for k, v in obj.items(): for k, v in obj.items():
etree.SubElement(node, k).text = v etree.SubElement(node, k).text = v
def get_base_attributes(entity):
"""Build a dict of attributes of an entity.
Returns attributes and their values, ignoring any properties, functions and anything that starts
with an underscore.
"""
attributes = {}
cls = entity.__class__
for attr, _ in inspect.getmembers(cls, lambda o: not isinstance(o, property) and not inspect.isroutine(o)):
if not attr.startswith("_"):
attributes[attr] = getattr(entity, attr)
return attributes

Wyświetl plik

@ -1,15 +1,16 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from datetime import datetime from datetime import datetime
import pytest
from federation.entities.base import Comment, Post, Reaction, Relationship from federation.entities.base import Comment, Post, Reaction, Relationship
from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest from federation.entities.diaspora.entities import DiasporaPost, DiasporaComment, DiasporaLike, DiasporaRequest
from federation.entities.diaspora.mappers import message_to_objects from federation.entities.diaspora.mappers import message_to_objects, get_outbound_entity
from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT, DIASPORA_POST_LIKE, \ from federation.tests.fixtures.payloads import DIASPORA_POST_SIMPLE, DIASPORA_POST_COMMENT, DIASPORA_POST_LIKE, \
DIASPORA_REQUEST DIASPORA_REQUEST
class TestDiasporaEntityMappersReceive(object): class TestDiasporaEntityMappersReceive(object):
def test_message_to_objects_simple_post(self): def test_message_to_objects_simple_post(self):
entities = message_to_objects(DIASPORA_POST_SIMPLE) entities = message_to_objects(DIASPORA_POST_SIMPLE)
assert len(entities) == 1 assert len(entities) == 1
@ -61,3 +62,43 @@ class TestDiasporaEntityMappersReceive(object):
assert following.target_handle == "alice@alice.diaspora.example.org" assert following.target_handle == "alice@alice.diaspora.example.org"
assert sharing.relationship == "sharing" assert sharing.relationship == "sharing"
assert following.relationship == "following" assert following.relationship == "following"
class TestGetOutboundEntity(object):
def test_already_fine_entities_are_returned_as_is(self):
entity = DiasporaPost()
assert get_outbound_entity(entity) == entity
entity = DiasporaLike()
assert get_outbound_entity(entity) == entity
entity = DiasporaComment()
assert get_outbound_entity(entity) == entity
entity = DiasporaRequest()
assert get_outbound_entity(entity) == entity
def test_post_is_converted_to_diasporapost(self):
entity = Post()
assert isinstance(get_outbound_entity(entity), DiasporaPost)
def test_comment_is_converted_to_diasporacomment(self):
entity = Comment()
assert isinstance(get_outbound_entity(entity), DiasporaComment)
def test_reaction_of_like_is_converted_to_diasporaplike(self):
entity = Reaction(reaction="like")
assert isinstance(get_outbound_entity(entity), DiasporaLike)
def test_relationship_of_sharing_or_following_is_converted_to_diasporarequest(self):
entity = Relationship(relationship="sharing")
assert isinstance(get_outbound_entity(entity), DiasporaRequest)
entity = Relationship(relationship="following")
assert isinstance(get_outbound_entity(entity), DiasporaRequest)
def test_other_reaction_raises(self):
entity = Reaction(reaction="foo")
with pytest.raises(ValueError):
get_outbound_entity(entity)
def test_other_relation_raises(self):
entity = Relationship(relationship="foo")
with pytest.raises(ValueError):
get_outbound_entity(entity)

Wyświetl plik

@ -0,0 +1,12 @@
# -*- coding: utf-8 -*-
from federation.entities.base import Post
from federation.entities.diaspora.utils import get_base_attributes
class TestGetBaseAttributes(object):
def test_get_base_attributes_returns_only_intended_attributes(self):
entity = Post()
attrs = get_base_attributes(entity).keys()
assert set(attrs) == {
'created_at', 'guid', 'handle', 'location', 'photos', 'provider_display_name', 'public', 'raw_content'
}

Wyświetl plik

@ -11,7 +11,6 @@ from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD
class TestHandleReceiveProtocolIdentification(object): class TestHandleReceiveProtocolIdentification(object):
def test_handle_receive_routes_to_identified_protocol(self): def test_handle_receive_routes_to_identified_protocol(self):
payload = UNENCRYPTED_DIASPORA_PAYLOAD payload = UNENCRYPTED_DIASPORA_PAYLOAD
with patch.object( with patch.object(
@ -31,7 +30,6 @@ class TestHandleReceiveProtocolIdentification(object):
class TestHandleCreatePayloadBuildsAPayload(object): 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())
@ -42,3 +40,13 @@ class TestHandleCreatePayloadBuildsAPayload(object):
assert len(parts) == 2 assert len(parts) == 2
assert parts[0] == "xml" assert parts[0] == "xml"
assert len(parts[1]) > 0 assert len(parts[1]) > 0
@patch("federation.controllers.get_outbound_entity")
def test_handle_create_payload_calls_get_outbound_entity(self, mock_get_outbound_entity):
mock_get_outbound_entity.return_value = DiasporaPost()
from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld")
to_user = Mock(key=RSA.generate(2048).publickey())
entity = DiasporaPost()
handle_create_payload(from_user, to_user, entity)
assert mock_get_outbound_entity.called