Merge branch 'undo-follow' into 'master'

Support ActivityPub unfollow

See merge request jaywink/federation!146
merge-requests/147/head
Jason Robinson 2019-06-28 22:37:27 +00:00
commit 1e629dd09c
6 zmienionych plików z 95 dodań i 16 usunięć

Wyświetl plik

@ -37,6 +37,9 @@ class ActivitypubFollow(ActivitypubObjectMixin, Follow):
"""
Post receive hook - send back follow ack.
"""
if not self.following:
return
from federation.utils.activitypub import retrieve_and_parse_profile # Circulars
try:
from federation.utils.django import get_function_from_config
@ -78,13 +81,27 @@ class ActivitypubFollow(ActivitypubObjectMixin, Follow):
logger.exception("ActivitypubFollow.post_receive - Failed to send Accept back")
def to_as2(self) -> Dict:
as2 = {
"@context": CONTEXTS_DEFAULT,
"id": self.activity_id,
"type": self._type,
"actor": self.actor_id,
"object": self.target_id,
}
if self.following:
as2 = {
"@context": CONTEXTS_DEFAULT,
"id": self.activity_id,
"type": self._type,
"actor": self.actor_id,
"object": self.target_id,
}
else:
as2 = {
"@context": CONTEXTS_DEFAULT,
"id": self.activity_id,
"type": ActivityType.UNDO.value,
"actor": self.actor_id,
"object": {
"id": f"{self.actor_id}#follow-{uuid.uuid4()}",
"type": self._type,
"actor": self.actor_id,
"object": self.target_id,
},
}
return as2

Wyświetl plik

@ -14,10 +14,11 @@ logger = logging.getLogger("federation")
MAPPINGS = {
"Accept": ActivitypubAccept,
"Article": ActivitypubPost,
"Follow": ActivitypubFollow,
"Follow": ActivitypubFollow, # Technically not correct, but for now we support only following profiles
"Note": ActivitypubPost,
"Page": ActivitypubPost,
"Person": ActivitypubProfile,
"Undo": ActivitypubFollow, # Technically not correct, but for now we support only undoing a follow of a profile
}
@ -158,8 +159,10 @@ def transform_attribute(key: str, value: Union[str, Dict, int], transformed: Dic
transformed["name"] = value
elif key == "object":
if isinstance(value, dict):
if cls in (ActivitypubAccept, ActivitypubFollow):
if cls == ActivitypubAccept:
transformed["target_id"] = value.get("id")
elif cls == ActivitypubFollow:
transformed["target_id"] = value.get("object")
else:
transform_attributes(value, cls, transformed, is_object=True)
else:
@ -173,6 +176,9 @@ def transform_attribute(key: str, value: Union[str, Dict, int], transformed: Dic
elif key in ("to", "cc"):
if isinstance(value, list) and NAMESPACE_PUBLIC in value:
transformed["public"] = True
elif key == "type":
if value == "Undo":
transformed["following"] = False
elif key == "url":
transformed["url"] = value

Wyświetl plik

@ -26,6 +26,32 @@ class TestEntitiesConvertToAS2:
},
}
def test_follow_to_as2(self, activitypubfollow):
result = activitypubfollow.to_as2()
assert result == {
"@context": CONTEXTS_DEFAULT,
"id": "https://localhost/follow",
"type": "Follow",
"actor": "https://localhost/profile",
"object": "https://example.com/profile"
}
def test_follow_to_as2__undo(self, activitypubundofollow):
result = activitypubundofollow.to_as2()
result["object"]["id"] = "https://localhost/follow" # Real object will have a random UUID postfix here
assert result == {
"@context": CONTEXTS_DEFAULT,
"id": "https://localhost/undo",
"type": "Undo",
"actor": "https://localhost/profile",
"object": {
"id": "https://localhost/follow",
"type": "Follow",
"actor": "https://localhost/profile",
"object": "https://example.com/profile",
}
}
def test_post_to_as2(self, activitypubpost):
result = activitypubpost.to_as2()
assert result == {

Wyświetl plik

@ -5,7 +5,8 @@ import pytest
from federation.entities.activitypub.entities import ActivitypubFollow, ActivitypubAccept, ActivitypubProfile
from federation.entities.activitypub.mappers import message_to_objects, get_outbound_entity
from federation.entities.base import Accept, Follow, Profile
from federation.tests.fixtures.payloads import ACTIVITYPUB_FOLLOW, ACTIVITYPUB_PROFILE, ACTIVITYPUB_PROFILE_INVALID
from federation.tests.fixtures.payloads import (
ACTIVITYPUB_FOLLOW, ACTIVITYPUB_PROFILE, ACTIVITYPUB_PROFILE_INVALID, ACTIVITYPUB_UNDO_FOLLOW)
class TestActivitypubEntityMappersReceive:
@ -23,6 +24,15 @@ class TestActivitypubEntityMappersReceive:
assert entity.target_id == "https://example.org/actor"
assert entity.following is True
def test_message_to_objects__unfollow(self):
entities = message_to_objects(ACTIVITYPUB_UNDO_FOLLOW, "https://example.com/actor")
assert len(entities) == 1
entity = entities[0]
assert isinstance(entity, ActivitypubFollow)
assert entity.actor_id == "https://example.com/actor"
assert entity.target_id == "https://example.org/actor"
assert entity.following is False
@pytest.mark.skip
def test_message_to_objects_mentions_are_extracted(self):
entities = message_to_objects(

Wyświetl plik

@ -58,6 +58,16 @@ def activitypubprofile():
)
@pytest.fixture
def activitypubundofollow():
return ActivitypubFollow(
activity_id="https://localhost/undo",
actor_id="https://localhost/profile",
target_id="https://example.com/profile",
following=False,
)
@pytest.fixture
def profile():
return Profile(

Wyświetl plik

@ -7,12 +7,6 @@ ACTIVITYPUB_FOLLOW = {
"type": "Follow",
"actor": "https://example.com/actor",
"object": "https://example.org/actor",
"signature": {
"type": "RsaSignature2017",
"creator": "https://example.com/actor#main-key",
"created": "2018-10-11T15:59:32Z",
"signatureValue": "foobar"
}
}
ACTIVITYPUB_PROFILE = {
@ -99,3 +93,19 @@ ACTIVITYPUB_PROFILE_INVALID = {
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwVbaT5wvaZobfIB044ai\nhJg/XooEn2jSTnTY1K4mPmhdqYUmszpdXKp64OwA+f3SBuIUIkLAYUSB9Fu19zh+\nzOsoGI5gvA32DHY1vaqdKnT9gt3jKS5AdQ3bl0t9f4pPkO2I5YtQOWV1FvBcwPXG\nB0dIqj0fTqNK37FmyybrRD6uhjySddklN9gNsULTqYVDa0QSXVswTIW2jQudnNlp\nnEf3SfjlK9J8eKPF3hFK3PNXBTTZ4NydBSL3cVBinU0cFg8lUJOK8RI4qaetrVoQ\neKd7gCTSQ7RZh8kmkYmdlweb+ZtORT6Y5ZsotR8jwhAOFAqCt36B5+LX2UIw68Pk\nOwIDAQAB\n-----END PUBLIC KEY-----\n"
},
}
ACTIVITYPUB_UNDO_FOLLOW = {
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
],
"id": "https://example.com/undo",
"type": "Undo",
"actor": "https://example.com/actor",
"object": {
"id": "https://example.com/follow",
"type": "Follow",
"actor": "https://example.com/actor",
"object": "https://example.org/actor",
},
}