kopia lustrzana https://gitlab.com/jaywink/federation
Merge branch 'activitypub-retractions' into 'master'
Add support for Retraction to/from ActivityPub See merge request jaywink/federation!150merge-requests/151/head
commit
d9637a6ae5
|
@ -49,6 +49,8 @@
|
|||
|
||||
* Network helper utility `fetch_document` can now also take a dictionary of `headers`. They will be passed to the underlying `requests` method call as is.
|
||||
|
||||
* `Retraction` entity can now also have an `entity_type` of `Object`. Receivers will need to find the correct object using `target_id` only. This is currently only relevant for ActivityPub where retraction messages do not refer to object type.
|
||||
|
||||
### Removed
|
||||
|
||||
* **Backwards incompatible.** Support for Legacy Diaspora payloads have been removed to reduce the amount of code needed to maintain while refactoring for ActivityPub.
|
||||
|
|
|
@ -10,7 +10,7 @@ from federation.entities.activitypub.constants import (
|
|||
from federation.entities.activitypub.enums import ActorType, ObjectType, ActivityType
|
||||
from federation.entities.activitypub.mixins import ActivitypubObjectMixin, ActivitypubActorMixin
|
||||
from federation.entities.activitypub.objects import ImageObject
|
||||
from federation.entities.base import Profile, Post, Follow, Accept, Comment
|
||||
from federation.entities.base import Profile, Post, Follow, Accept, Comment, Retraction
|
||||
from federation.outbound import handle_send
|
||||
from federation.types import UserType
|
||||
from federation.utils.text import with_slash
|
||||
|
@ -206,3 +206,21 @@ class ActivitypubProfile(ActivitypubActorMixin, Profile):
|
|||
except Exception as ex:
|
||||
logger.warning("ActivitypubProfile.to_as2 - failed to set profile icon: %s", ex)
|
||||
return as2
|
||||
|
||||
|
||||
class ActivitypubRetraction(ActivitypubObjectMixin, Retraction):
|
||||
_type = ObjectType.TOMBSTONE.value
|
||||
|
||||
def to_as2(self) -> Dict:
|
||||
as2 = {
|
||||
"@context": CONTEXTS_DEFAULT,
|
||||
"id": self.activity_id,
|
||||
"type": ActivityType.DELETE.value,
|
||||
"actor": self.actor_id,
|
||||
"object": {
|
||||
"id": self.target_id,
|
||||
"type": self._type,
|
||||
},
|
||||
"published": self.created_at.isoformat(),
|
||||
}
|
||||
return as2
|
||||
|
|
|
@ -22,3 +22,4 @@ class ActorType(EnumBase):
|
|||
|
||||
class ObjectType(EnumBase):
|
||||
NOTE = "Note"
|
||||
TOMBSTONE = "Tombstone"
|
||||
|
|
|
@ -3,8 +3,9 @@ from typing import List, Callable, Dict, Union
|
|||
|
||||
from federation.entities.activitypub.constants import NAMESPACE_PUBLIC
|
||||
from federation.entities.activitypub.entities import (
|
||||
ActivitypubFollow, ActivitypubProfile, ActivitypubAccept, ActivitypubPost, ActivitypubComment)
|
||||
from federation.entities.base import Follow, Profile, Accept, Post, Comment
|
||||
ActivitypubFollow, ActivitypubProfile, ActivitypubAccept, ActivitypubPost, ActivitypubComment,
|
||||
ActivitypubRetraction)
|
||||
from federation.entities.base import Follow, Profile, Accept, Post, Comment, Retraction
|
||||
from federation.entities.mixins import BaseEntity
|
||||
from federation.types import UserType
|
||||
|
||||
|
@ -14,6 +15,7 @@ logger = logging.getLogger("federation")
|
|||
MAPPINGS = {
|
||||
"Accept": ActivitypubAccept,
|
||||
"Article": ActivitypubPost,
|
||||
"Delete": ActivitypubRetraction,
|
||||
"Follow": ActivitypubFollow, # Technically not correct, but for now we support only following profiles
|
||||
"Note": ActivitypubPost,
|
||||
"Page": ActivitypubPost,
|
||||
|
@ -27,7 +29,9 @@ def element_to_objects(payload: Dict) -> List:
|
|||
Transform an Element to a list of entities recursively.
|
||||
"""
|
||||
entities = []
|
||||
if isinstance(payload.get('object'), dict) and payload["object"].get('type'):
|
||||
if payload.get('type') == "Delete":
|
||||
cls = ActivitypubRetraction
|
||||
elif isinstance(payload.get('object'), dict) and payload["object"].get('type'):
|
||||
if payload["object"]["type"] == "Note" and payload["object"].get("inReplyTo"):
|
||||
cls = ActivitypubComment
|
||||
else:
|
||||
|
@ -80,7 +84,10 @@ def get_outbound_entity(entity: BaseEntity, private_key):
|
|||
return entity
|
||||
outbound = None
|
||||
cls = entity.__class__
|
||||
if cls in [ActivitypubAccept, ActivitypubFollow, ActivitypubProfile, ActivitypubPost, ActivitypubComment]:
|
||||
if cls in [
|
||||
ActivitypubAccept, ActivitypubFollow, ActivitypubProfile, ActivitypubPost, ActivitypubComment,
|
||||
ActivitypubRetraction,
|
||||
]:
|
||||
# Already fine
|
||||
outbound = entity
|
||||
elif cls == Accept:
|
||||
|
@ -91,6 +98,8 @@ def get_outbound_entity(entity: BaseEntity, private_key):
|
|||
outbound = ActivitypubPost.from_base(entity)
|
||||
elif cls == Profile:
|
||||
outbound = ActivitypubProfile.from_base(entity)
|
||||
elif cls == Retraction:
|
||||
outbound = ActivitypubRetraction.from_base(entity)
|
||||
elif cls == Comment:
|
||||
outbound = ActivitypubComment.from_base(entity)
|
||||
if not outbound:
|
||||
|
@ -121,7 +130,13 @@ def transform_attribute(key: str, value: Union[str, Dict, int], transformed: Dic
|
|||
if value is None:
|
||||
value = ""
|
||||
if key == "id":
|
||||
if is_object or cls == ActivitypubProfile:
|
||||
if is_object:
|
||||
if cls == ActivitypubRetraction:
|
||||
transformed["target_id"] = value
|
||||
transformed["entity_type"] = "Object"
|
||||
else:
|
||||
transformed["id"] = value
|
||||
elif cls == ActivitypubProfile:
|
||||
transformed["id"] = value
|
||||
else:
|
||||
transformed["activity_id"] = value
|
||||
|
|
|
@ -139,6 +139,22 @@ class TestEntitiesConvertToAS2:
|
|||
}
|
||||
}
|
||||
|
||||
def test_retraction_to_as2(self, activitypubretraction):
|
||||
result = activitypubretraction.to_as2()
|
||||
assert result == {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
],
|
||||
'type': 'Delete',
|
||||
'id': 'http://127.0.0.1:8000/post/123456/#delete',
|
||||
'actor': 'http://127.0.0.1:8000/profile/123456/',
|
||||
'object': {
|
||||
'id': 'http://127.0.0.1:8000/post/123456/',
|
||||
'type': 'Tombstone',
|
||||
},
|
||||
'published': '2019-04-27T00:00:00',
|
||||
}
|
||||
|
||||
|
||||
class TestEntitiesPostReceive:
|
||||
@patch("federation.utils.activitypub.retrieve_and_parse_profile", autospec=True)
|
||||
|
|
|
@ -3,12 +3,13 @@ from unittest.mock import patch
|
|||
import pytest
|
||||
|
||||
from federation.entities.activitypub.entities import (
|
||||
ActivitypubFollow, ActivitypubAccept, ActivitypubProfile, ActivitypubPost, ActivitypubComment)
|
||||
ActivitypubFollow, ActivitypubAccept, ActivitypubProfile, ActivitypubPost, ActivitypubComment,
|
||||
ActivitypubRetraction)
|
||||
from federation.entities.activitypub.mappers import message_to_objects, get_outbound_entity
|
||||
from federation.entities.base import Accept, Follow, Profile, Post, Comment
|
||||
from federation.tests.fixtures.payloads import (
|
||||
ACTIVITYPUB_FOLLOW, ACTIVITYPUB_PROFILE, ACTIVITYPUB_PROFILE_INVALID, ACTIVITYPUB_UNDO_FOLLOW, ACTIVITYPUB_POST,
|
||||
ACTIVITYPUB_COMMENT)
|
||||
ACTIVITYPUB_COMMENT, ACTIVITYPUB_RETRACTION)
|
||||
|
||||
|
||||
class TestActivitypubEntityMappersReceive:
|
||||
|
@ -150,15 +151,14 @@ class TestActivitypubEntityMappersReceive:
|
|||
entity = entities[0]
|
||||
assert entity._receiving_actor_id == "bob@example.com"
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_message_to_objects_retraction(self):
|
||||
entities = message_to_objects(DIASPORA_RETRACTION, "bob@example.com")
|
||||
entities = message_to_objects(ACTIVITYPUB_RETRACTION, "https://friendica.feneas.org/profile/jaywink")
|
||||
assert len(entities) == 1
|
||||
entity = entities[0]
|
||||
assert isinstance(entity, DiasporaRetraction)
|
||||
assert entity.handle == "bob@example.com"
|
||||
assert entity.target_guid == "x" * 16
|
||||
assert entity.entity_type == "Post"
|
||||
assert isinstance(entity, ActivitypubRetraction)
|
||||
assert entity.actor_id == "https://friendica.feneas.org/profile/jaywink"
|
||||
assert entity.target_id == "https://friendica.feneas.org/objects/76158462-165d-3386-aa23-ba2090614385"
|
||||
assert entity.entity_type == "Object"
|
||||
|
||||
@pytest.mark.skip
|
||||
def test_message_to_objects_accounce(self):
|
||||
|
|
|
@ -2,7 +2,8 @@ import pytest
|
|||
from freezegun import freeze_time
|
||||
|
||||
from federation.entities.activitypub.entities import (
|
||||
ActivitypubPost, ActivitypubAccept, ActivitypubFollow, ActivitypubProfile, ActivitypubComment)
|
||||
ActivitypubPost, ActivitypubAccept, ActivitypubFollow, ActivitypubProfile, ActivitypubComment,
|
||||
ActivitypubRetraction)
|
||||
from federation.entities.base import Profile
|
||||
from federation.entities.diaspora.entities import (
|
||||
DiasporaPost, DiasporaComment, DiasporaLike, DiasporaProfile, DiasporaRetraction,
|
||||
|
@ -72,6 +73,17 @@ def activitypubprofile():
|
|||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubretraction():
|
||||
with freeze_time("2019-04-27"):
|
||||
return ActivitypubRetraction(
|
||||
target_id="http://127.0.0.1:8000/post/123456/",
|
||||
activity_id="http://127.0.0.1:8000/post/123456/#delete",
|
||||
actor_id="http://127.0.0.1:8000/profile/123456/",
|
||||
entity_type="Post",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def activitypubundofollow():
|
||||
return ActivitypubFollow(
|
||||
|
|
|
@ -144,6 +144,45 @@ ACTIVITYPUB_PROFILE_INVALID = {
|
|||
},
|
||||
}
|
||||
|
||||
ACTIVITYPUB_RETRACTION = {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
'https://w3id.org/security/v1',
|
||||
{
|
||||
'vcard': 'http://www.w3.org/2006/vcard/ns#',
|
||||
'dfrn': 'http://purl.org/macgirvin/dfrn/1.0/',
|
||||
'diaspora': 'https://diasporafoundation.org/ns/',
|
||||
'litepub': 'http://litepub.social/ns#',
|
||||
'manuallyApprovesFollowers': 'as:manuallyApprovesFollowers',
|
||||
'sensitive': 'as:sensitive',
|
||||
'Hashtag': 'as:Hashtag',
|
||||
'directMessage': 'litepub:directMessage',
|
||||
},
|
||||
],
|
||||
'id': 'https://friendica.feneas.org/objects/76158462-165d-3386-aa23-ba2090614385#Delete',
|
||||
'type': 'Delete',
|
||||
'actor': 'https://friendica.feneas.org/profile/jaywink',
|
||||
'published': '2019-07-20T21:24:58Z',
|
||||
'instrument': {
|
||||
'type': 'Service',
|
||||
'name': "Friendica 'Dalmatian Bellflower' 2019.06-1313",
|
||||
'url': 'https://friendica.feneas.org',
|
||||
},
|
||||
'to': ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
'cc': ['https://friendica.feneas.org/followers/jaywink'],
|
||||
'object': {
|
||||
'id': 'https://friendica.feneas.org/objects/76158462-165d-3386-aa23-ba2090614385',
|
||||
'type': 'Tombstone',
|
||||
},
|
||||
'signature': {
|
||||
'type': 'RsaSignature2017',
|
||||
'nonce': 'de299d5c8074548d8022d31059b4735870f29ea85d78c5214a423038273c5e5c',
|
||||
'creator': 'https://friendica.feneas.org/profile/jaywink#main-key',
|
||||
'created': '2019-07-20T21:39:13Z',
|
||||
'signatureValue': 'lotsoftext',
|
||||
},
|
||||
}
|
||||
|
||||
ACTIVITYPUB_UNDO_FOLLOW = {
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
|
|
Ładowanie…
Reference in New Issue