kopia lustrzana https://gitlab.com/jaywink/federation
Entities of type `Profile` now have a dictionary of `inboxes`
With two elements, `private` and `public`. These should be URL's indicating where to send payloads for the recipient. ActivityPub profiles will parse these values from incoming profile documents. Diaspora entities will default to the inboxes in the specification.merge-requests/143/head
rodzic
9df3fe5c1a
commit
80c4e433d7
|
@ -18,6 +18,10 @@
|
|||
|
||||
* Added network utility `network.fetch_host_ip` to fetch IP by hostname.
|
||||
|
||||
* Entities of type `Profile` now have a dictionary of `inboxes`, with two elements, `private` and `public`. These should be URL's indicating where to send payloads for the recipient.
|
||||
|
||||
ActivityPub profiles will parse these values from incoming profile documents. Diaspora entities will default to the inboxes in the specification.
|
||||
|
||||
### Changed
|
||||
|
||||
* **Backwards incompatible.** Lowest compatible Python version is now 3.6.
|
||||
|
|
|
@ -107,12 +107,12 @@ class ActivitypubProfile(ActivitypubEntityMixin, Profile):
|
|||
CONTEXT_MANUALLY_APPROVES_FOLLOWERS,
|
||||
],
|
||||
"endpoints": {
|
||||
"sharedInbox": f"{with_slash(self.base_url)}ap/inbox/", # TODO just get from config
|
||||
"sharedInbox": self.inboxes["public"],
|
||||
},
|
||||
"followers": f"{with_slash(self.id)}followers/",
|
||||
"following": f"{with_slash(self.id)}following/",
|
||||
"id": self.id,
|
||||
"inbox": f"{with_slash(self.id)}inbox/",
|
||||
"inbox": self.inboxes["private"],
|
||||
"manuallyApprovesFollowers": False,
|
||||
"name": self.name,
|
||||
"outbox": f"{with_slash(self.id)}outbox/",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import List, Callable, Dict
|
||||
from typing import List, Callable, Dict, Union
|
||||
|
||||
from federation.entities.activitypub.entities import ActivitypubFollow, ActivitypubProfile, ActivitypubAccept
|
||||
from federation.entities.base import Follow, Profile, Accept
|
||||
|
@ -85,51 +85,59 @@ def message_to_objects(
|
|||
return element_to_objects(message, sender, sender_key_fetcher, user)
|
||||
|
||||
|
||||
def transform_attribute(key, value, cls):
|
||||
def transform_attribute(key: str, value: Union[str, Dict, int], transformed: Dict, cls) -> None:
|
||||
if value is None:
|
||||
value = ""
|
||||
if key == "id":
|
||||
if cls == ActivitypubProfile:
|
||||
return {"id": value}
|
||||
transformed["id"] = value
|
||||
else:
|
||||
return {"activity_id": value}
|
||||
transformed["activity_id"] = value
|
||||
elif key == "actor":
|
||||
return {"actor_id": value}
|
||||
transformed["actor_id"] = value
|
||||
elif key == "inboxes" and isinstance(value, dict):
|
||||
if "inboxes" not in transformed:
|
||||
transformed["inboxes"] = {"private": None, "public": None}
|
||||
transformed["endpoints"]["public"] = value.get("sharedInbox")
|
||||
elif key == "icon":
|
||||
# TODO maybe we should ditch these size constants and instead have a more flexible dict for images
|
||||
# so based on protocol there would either be one url or many by size name
|
||||
if isinstance(value, dict):
|
||||
return {"image_urls": {
|
||||
transformed["image_urls"] = {
|
||||
"small": value['url'],
|
||||
"medium": value['url'],
|
||||
"large": value['url'],
|
||||
}}
|
||||
}
|
||||
else:
|
||||
return {"image_urls": {
|
||||
transformed["image_urls"] = {
|
||||
"small": value,
|
||||
"medium": value,
|
||||
"large": value,
|
||||
}}
|
||||
}
|
||||
elif key == "inbox":
|
||||
if "inboxes" not in transformed:
|
||||
transformed["inboxes"] = {"private": None, "public": None}
|
||||
transformed["inboxes"]["private"] = value
|
||||
elif key == "name":
|
||||
return {"name": value}
|
||||
transformed["name"] = value
|
||||
elif key == "object":
|
||||
if isinstance(value, dict):
|
||||
return transform_attributes(value, cls)
|
||||
transform_attributes(value, cls, transformed)
|
||||
else:
|
||||
return {"target_id": value}
|
||||
transformed["target_id"] = value
|
||||
elif key == "preferredUsername":
|
||||
return {"username": value}
|
||||
transformed["username"] = value
|
||||
elif key == "publicKey":
|
||||
return {"public_key": value.get('publicKeyPem', '')}
|
||||
transformed["public_key"] = value.get('publicKeyPem', '')
|
||||
elif key == "summary":
|
||||
return {"raw_content": value}
|
||||
transformed["raw_content"] = value
|
||||
elif key == "url":
|
||||
return {"url": value}
|
||||
return {}
|
||||
transformed["url"] = value
|
||||
|
||||
|
||||
def transform_attributes(payload, cls):
|
||||
transformed = {}
|
||||
def transform_attributes(payload: Dict, cls, transformed: Dict = None) -> Dict:
|
||||
if not transformed:
|
||||
transformed = {}
|
||||
for key, value in payload.items():
|
||||
transformed.update(transform_attribute(key, value, cls))
|
||||
transform_attribute(key, value, transformed, cls)
|
||||
return transformed
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from typing import Dict
|
||||
|
||||
from dirty_validators.basic import Email
|
||||
|
||||
from federation.entities.mixins import (
|
||||
|
@ -105,6 +107,7 @@ class Profile(CreatedAtMixin, OptionalRawContentMixin, PublicMixin):
|
|||
tag_list = None
|
||||
url = ""
|
||||
username = ""
|
||||
inboxes: Dict = None
|
||||
|
||||
_allowed_children = (Image,)
|
||||
|
||||
|
@ -112,6 +115,10 @@ class Profile(CreatedAtMixin, OptionalRawContentMixin, PublicMixin):
|
|||
self.image_urls = {
|
||||
"small": "", "medium": "", "large": ""
|
||||
}
|
||||
self.inboxes = {
|
||||
"private": None,
|
||||
"public": None,
|
||||
}
|
||||
self.tag_list = []
|
||||
super().__init__(*args, **kwargs)
|
||||
# As an exception, a Profile does not require to have an `actor_id`
|
||||
|
|
|
@ -4,6 +4,7 @@ from federation.entities.base import (
|
|||
Comment, Post, Reaction, Profile, Retraction, Follow, Share, Image)
|
||||
from federation.entities.diaspora.mixins import DiasporaEntityMixin, DiasporaRelayableMixin
|
||||
from federation.entities.diaspora.utils import format_dt, struct_to_xml
|
||||
from federation.utils.diaspora import get_private_endpoint, get_public_endpoint
|
||||
|
||||
|
||||
class DiasporaComment(DiasporaRelayableMixin, Comment):
|
||||
|
@ -89,6 +90,13 @@ class DiasporaProfile(DiasporaEntityMixin, Profile):
|
|||
"""Diaspora profile."""
|
||||
_tag_name = "profile"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.inboxes = {
|
||||
"private": get_private_endpoint(self.handle, self.guid),
|
||||
"public": get_public_endpoint(self.handle),
|
||||
}
|
||||
|
||||
def to_xml(self):
|
||||
"""Convert to XML message."""
|
||||
element = etree.Element(self._tag_name)
|
||||
|
|
|
@ -237,7 +237,7 @@ class TestGetOutboundEntity:
|
|||
assert get_outbound_entity(entity, private_key) == entity
|
||||
entity = DiasporaComment()
|
||||
assert get_outbound_entity(entity, private_key) == entity
|
||||
entity = DiasporaProfile()
|
||||
entity = DiasporaProfile(handle="foobar@example.com", guid="1234")
|
||||
assert get_outbound_entity(entity, private_key) == entity
|
||||
entity = DiasporaContact()
|
||||
assert get_outbound_entity(entity, private_key) == entity
|
||||
|
@ -258,7 +258,7 @@ class TestGetOutboundEntity:
|
|||
|
||||
|
||||
def test_profile_is_converted_to_diasporaprofile(self, private_key):
|
||||
entity = Profile()
|
||||
entity = Profile(handle="foobar@example.com", guid="1234")
|
||||
assert isinstance(get_outbound_entity(entity, private_key), DiasporaProfile)
|
||||
|
||||
def test_other_reaction_raises(self, private_key):
|
||||
|
|
|
@ -25,6 +25,7 @@ class TestGetBaseAttributes:
|
|||
"created_at", "name", "email", "gender", "raw_content", "location", "public",
|
||||
"nsfw", "public_key", "image_urls", "tag_list", "signature", "url", "atom_url",
|
||||
"base_url", "id", "actor_id", "handle", "handle", "guid", "activity", "activity_id", "username",
|
||||
"inboxes",
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -43,7 +43,9 @@ class TestHandleSend:
|
|||
# Ensure second call is a private activitypub payload
|
||||
args, kwargs = mock_send.call_args_list[1]
|
||||
assert args[0] == "https://127.0.0.1/foobar"
|
||||
assert kwargs['headers'] == {'Content-Type': 'application/activity+json'}
|
||||
assert kwargs['headers'] == {
|
||||
'Content-Type': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
}
|
||||
|
||||
# Ensure public payloads and recipients, one per unique host
|
||||
args1, kwargs1 = mock_send.call_args_list[2]
|
||||
|
@ -57,6 +59,8 @@ class TestHandleSend:
|
|||
}
|
||||
assert args2[1].startswith("<me:env xmlns:me=")
|
||||
assert args3[1].startswith("<me:env xmlns:me=")
|
||||
assert kwargs1['headers'] == {'Content-Type': 'application/activity+json'}
|
||||
assert kwargs1['headers'] == {
|
||||
'Content-Type': 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
}
|
||||
assert kwargs2['headers'] == {'Content-Type': 'application/magic-envelope+xml'}
|
||||
assert kwargs3['headers'] == {'Content-Type': 'application/magic-envelope+xml'}
|
||||
|
|
Ładowanie…
Reference in New Issue