kopia lustrzana https://gitlab.com/jaywink/federation
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 #27merge-requests/130/head
rodzic
8b880f89a0
commit
bf348e9544
|
@ -5,6 +5,7 @@
|
|||
|
||||
## Changed
|
||||
- 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
|
||||
- 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.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import importlib
|
||||
|
||||
from federation.entities.diaspora.mappers import get_outbound_entity
|
||||
from federation.exceptions import NoSuitableProtocolFoundError
|
||||
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.
|
||||
`to_user` must have `key` attribute.
|
||||
"""
|
||||
# Just use Diaspora protocol for now
|
||||
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
|
||||
|
|
|
@ -2,10 +2,16 @@
|
|||
from lxml import etree
|
||||
|
||||
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."""
|
||||
author_signature = ""
|
||||
|
||||
|
@ -21,7 +27,7 @@ class DiasporaComment(Comment):
|
|||
return element
|
||||
|
||||
|
||||
class DiasporaPost(Post):
|
||||
class DiasporaPost(DiasporaEntityMixin, Post):
|
||||
"""Diaspora post, ie status message."""
|
||||
def to_xml(self):
|
||||
"""Convert to XML message."""
|
||||
|
@ -36,7 +42,7 @@ class DiasporaPost(Post):
|
|||
return element
|
||||
|
||||
|
||||
class DiasporaLike(Reaction):
|
||||
class DiasporaLike(DiasporaEntityMixin, Reaction):
|
||||
"""Diaspora like."""
|
||||
author_signature = ""
|
||||
reaction = "like"
|
||||
|
@ -55,7 +61,7 @@ class DiasporaLike(Reaction):
|
|||
return element
|
||||
|
||||
|
||||
class DiasporaRequest(Relationship):
|
||||
class DiasporaRequest(DiasporaEntityMixin, Relationship):
|
||||
"""Diaspora relationship request."""
|
||||
relationship = "sharing"
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ from datetime import datetime
|
|||
|
||||
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
|
||||
|
||||
MAPPINGS = {
|
||||
|
@ -69,3 +69,33 @@ def transform_attributes(attrs):
|
|||
else:
|
||||
transformed[key] = value
|
||||
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.")
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import inspect
|
||||
|
||||
from dateutil.tz import tzlocal, tzutc
|
||||
from lxml import etree
|
||||
|
||||
|
@ -32,3 +34,17 @@ def struct_to_xml(node, struct):
|
|||
for obj in struct:
|
||||
for k, v in obj.items():
|
||||
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
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from federation.entities.base import Comment, Post, Reaction, Relationship
|
||||
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, \
|
||||
DIASPORA_REQUEST
|
||||
|
||||
|
||||
class TestDiasporaEntityMappersReceive(object):
|
||||
|
||||
def test_message_to_objects_simple_post(self):
|
||||
entities = message_to_objects(DIASPORA_POST_SIMPLE)
|
||||
assert len(entities) == 1
|
||||
|
@ -61,3 +62,43 @@ class TestDiasporaEntityMappersReceive(object):
|
|||
assert following.target_handle == "alice@alice.diaspora.example.org"
|
||||
assert sharing.relationship == "sharing"
|
||||
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)
|
||||
|
|
|
@ -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'
|
||||
}
|
|
@ -11,7 +11,6 @@ from federation.tests.fixtures.payloads import UNENCRYPTED_DIASPORA_PAYLOAD
|
|||
|
||||
|
||||
class TestHandleReceiveProtocolIdentification(object):
|
||||
|
||||
def test_handle_receive_routes_to_identified_protocol(self):
|
||||
payload = UNENCRYPTED_DIASPORA_PAYLOAD
|
||||
with patch.object(
|
||||
|
@ -31,7 +30,6 @@ class TestHandleReceiveProtocolIdentification(object):
|
|||
|
||||
|
||||
class TestHandleCreatePayloadBuildsAPayload(object):
|
||||
|
||||
def test_handle_create_payload_builds_an_xml(self):
|
||||
from_user = Mock(private_key=RSA.generate(2048), handle="foobar@domain.tld")
|
||||
to_user = Mock(key=RSA.generate(2048).publickey())
|
||||
|
@ -42,3 +40,13 @@ class TestHandleCreatePayloadBuildsAPayload(object):
|
|||
assert len(parts) == 2
|
||||
assert parts[0] == "xml"
|
||||
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
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue