diff --git a/CHANGELOG.md b/CHANGELOG.md index ff452a4..c78dee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,13 +13,13 @@ * GET requests are now signed if the django configuration includes FEDERATION_USER which is used to fetch that user's private key. -* Added Video and Audio objects. +* Added Video and Audio objects. Inbound support only. * Process Activitypub reply collections. ### Fixed -* Signatures are not verified and the corresponding payload is dropped is no public key is found. +* Signatures are not verified and the corresponding payload is dropped if no public key is found. ### Internal changes diff --git a/docs/development.rst b/docs/development.rst index faa9b0a..4b8e020 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -10,7 +10,7 @@ Help is more than welcome to extend this library. Please see the following resou Environment setup ----------------- -Once you have your (Python 3.6+) virtualenv set up, install the development requirements:: +Once you have your (Python 3.7+) virtualenv set up, install the development requirements:: pip install -r dev-requirements.txt @@ -34,7 +34,7 @@ Built documentation is available at ``docs/_build/html/index.html``. Contact for help ---------------- -Easiest via Matrix on room ``#socialhome:feneas.org``. There is a bridged +Easiest via Matrix on room ``#socialhome:federator.dev``. There is a bridged Freenode channel as well found at ``#socialhome``. You can also ask questions or give feedback via issues. diff --git a/docs/introduction.rst b/docs/introduction.rst index 83a2014..77a90e9 100644 --- a/docs/introduction.rst +++ b/docs/introduction.rst @@ -14,9 +14,8 @@ Status Currently three protocols are being focused on. * Diaspora is considered to be stable with most of the protocol implemented. -* ActivityPub support should be considered as alpha - all the basic - things work but there are likely to be a lot of compatibility issues with other ActivityPub - implementations. +* ActivityPub support should be considered as beta - inbound payload are + handled by a jsonld processor (calamus) * Matrix support cannot be considered usable as of yet. The code base is well tested and in use in several projects. Backward incompatible changes @@ -48,5 +47,5 @@ License Author ...... -Jason Robinson / `jasonrobinson.me `_ / `@jaywink:federator.dev `_ / `GitLab `_ / `GitHub `_ +Jason Robinson / `jasonrobinson.me `_ / `@jaywink:federator.dev `_ / `GitLab `_ / `GitHub `_ diff --git a/docs/protocols.rst b/docs/protocols.rst index 97fab6c..1e15467 100644 --- a/docs/protocols.rst +++ b/docs/protocols.rst @@ -48,9 +48,15 @@ Features currently supported: * Actor (Person outbound, Person, Organization, Service inbound) * Note, Article and Page (Create, Delete, Update) * These become a ``Post`` or ``Comment`` depending on ``inReplyTo``. - * Attachment images from the above objects + * Attachment images, (inbound only for audios and videos) from the above objects * Follow, Accept Follow, Undo Follow * Announce + * Inbound Peertube Video objects translated as ``Post``. + +* Inbound processing of reply collections, for platforms that implement it. +* Link, Like, View, Signature, PropertyValue, IdentityProof and Emojis objects are only processed for inbound + payloads currently. Outbound processing requires support by the client + application. Namespace ......... @@ -71,23 +77,26 @@ The following keys will be set on the entity based on the ``source`` property ex * ``_rendered_content`` will be the object ``content`` * ``raw_content`` will object ``content`` run through a HTML2Markdown renderer +The ``contentMap`` property is processed but content language selection is not implemented yet. + For outbound entities, ``raw_content`` is expected to be in ``text/markdown``, specifically CommonMark. When sending payloads, ``raw_content`` will be rendered via the ``commonmark`` library into ``object.content``. The original ``raw_content`` will be added to the ``object.source`` property. -Images +Medias ...... Any images referenced in the ``raw_content`` of outbound entities will be extracted -into ``object.attachment`` objects, for receivers that don't support inline images. -These attachments will have a ``pyfed:inlineImage`` property set to ``true`` to -indicate the image has been extrated from the content. Receivers should ignore the +into ``object.attachment`` object. For receivers that don't support inline images, +image attachments will have a ``pyfed:inlineImage`` property set to ``true`` to +indicate the image has been extracted from the content. Receivers should ignore the inline image attachments if they support showing ```` HTML tags or the markdown -content in ``object.source``. +content in ``object.source``. Outbound audio and video attachments currently lack +support from client applications. -For inbound entities we do this automatically by not including received attachments in -the entity ``_children`` attribute. +For inbound entities we do this automatically by not including received image attachments in +the entity ``_children`` attribute. Audio and video are passed through the client application. .. _matrix: diff --git a/docs/usage.rst b/docs/usage.rst index 9ed45b1..5126a67 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -37,7 +37,7 @@ passed back to the caller. For sending messages out, either base or protocol specific entities can be passed to the outbound senders. -If you need the correct protocol speficic entity class from the base entity, +If you need the correct protocol specific entity class from the base entity, each protocol will define a ``get_outbound_entity`` function. .. autofunction:: federation.entities.activitypub.mappers.get_outbound_entity @@ -212,6 +212,7 @@ Some settings need to be set in Django settings. An example is below: FEDERATION = { "base_url": "https://myserver.domain.tld, + "federation_id": "https://example.com/u/john/", "get_object_function": "myproject.utils.get_object", "get_private_key_function": "myproject.utils.get_private_key", "get_profile_function": "myproject.utils.get_profile", @@ -223,6 +224,7 @@ Some settings need to be set in Django settings. An example is below: } * ``base_url`` is the base URL of the server, ie protocol://domain.tld. +* ``federation_id`` is a valid ActivityPub local profile id whose private key will be used to create the HTTP signature for GET requests to ActivityPub platforms. * ``get_object_function`` should be the full path to a function that will return the object matching the ActivityPub ID for the request object passed to this function. * ``get_private_key_function`` should be the full path to a function that will accept a federation ID (url, handle or guid) and return the private key of the user (as an RSA object). Required for example to sign outbound messages in some cases. * ``get_profile_function`` should be the full path to a function that should return a ``Profile`` entity. The function should take one or more keyword arguments: ``fid``, ``handle``, ``guid`` or ``request``. It should look up a profile with one or more of the provided parameters. diff --git a/federation/entities/activitypub/entities.py b/federation/entities/activitypub/entities.py index 06cbdba..5087b6f 100644 --- a/federation/entities/activitypub/entities.py +++ b/federation/entities/activitypub/entities.py @@ -130,14 +130,6 @@ class ActivitypubNoteMixin(AttachImagesMixin, CleanContentMixin, PublicMixin, Cr if isinstance(tag, Mention): self._mentions.add(tag.href) - #if not isinstance(self._source_object, dict): - # return - #source = self._source_object.get('object') if isinstance(self._source_object.get('object'), dict) else \ - # self._source_object - #for tag in source.get('tag', []): - # if tag.get('type') == "Mention" and tag.get('href'): - # self._mentions.add(tag.get('href')) - def pre_send(self): super().pre_send() self.extract_mentions() diff --git a/federation/entities/activitypub/models.py b/federation/entities/activitypub/models.py index d616670..484634f 100644 --- a/federation/entities/activitypub/models.py +++ b/federation/entities/activitypub/models.py @@ -40,7 +40,7 @@ def get_loader(*args, **kwargs): backend = rc.SQLiteCache(db_path='fed_cache') except ImportError: backend = rc.SQLiteCache(db_path='fed_cache') - logger.info('Using %s for requests_cache', type(backend)) + logger.debug('Using %s for requests_cache', type(backend)) requests_loader = jsonld.requests_document_loader(*args, **kwargs) @@ -239,31 +239,31 @@ class Object(metaclass=JsonLDAnnotation): image = MixedField(as2.image, nested='ImageSchema') tag_list = MixedField(as2.tag, nested=['HashtagSchema','MentionSchema','PropertyValueSchema','EmojiSchema']) _children = fields.Nested(as2.attachment, nested=['ImageSchema', 'AudioSchema', 'DocumentSchema','PropertyValueSchema','IdentityProofSchema'], many=True) - #_children = MixedField(as2.attachment, nested=['ImageSchema', 'AudioSchema', 'DocumentSchema','PropertyValueSchema','IdentityProofSchema']) - #audience content_map = LanguageMap(as2.content) # language maps are not implemented in calamus context = IRI(as2.context) guid = fields.String(diaspora.guid) name = fields.String(as2.name) - #endtime generator = MixedField(as2.generator, nested='ServiceSchema') - #generator = Dict(as2.generator) - #location - #preview created_at = fields.DateTime(as2.published, add_value_types=True) replies = MixedField(as2.replies, nested=['CollectionSchema','OrderedCollectionSchema']) signature = MixedField(sec.signature, nested = 'SignatureSchema') start_time = fields.DateTime(as2.startTime, add_value_types=True) updated = fields.DateTime(as2.updated, add_value_types=True) to = IRI(as2.to) - #bto cc = IRI(as2.cc) - #bcc media_type = fields.String(as2.mediaType) - #duration sensitive = fields.Boolean(as2.sensitive) source = Dict(as2.source) + # The following properties are defined by some platforms, but are not implemented yet + #audience + #endtime + #location + #preview + #bto + #bcc + #duration + def __init__(self, *args, **kwargs): for k, v in kwargs.items(): if hasattr(self, k): @@ -461,8 +461,9 @@ class Link(metaclass=JsonLDAnnotation): width = Integer(as2.width, flavor=xsd.nonNegativeInteger, add_value_types=True) fps = Integer(pt.fps, flavor=schema.Number, add_value_types=True) size = Integer(pt.size, flavor=schema.Number, add_value_types=True) - #preview : variable type? tag = MixedField(as2.tag, nested=['InfohashSchema', 'LinkSchema']) + # Not implemented yet + #preview : variable type? class Meta: rdf_type = as2.Link @@ -513,16 +514,9 @@ class Person(Object): outbox = IRI(as2.outbox, dump_derived={'fmt': '{id}outbox/', 'fields': ['id']}) following = IRI(as2.following, dump_derived={'fmt': '{id}following/', 'fields': ['id']}) followers = IRI(as2.followers, dump_derived={'fmt': '{id}followers/', 'fields': ['id']}) - #liked is a collection - #streams username = fields.String(as2.preferredUsername) endpoints = Dict(as2.endpoints) shared_inbox = IRI(as2.sharedInbox) # misskey adds this - #proxyUrl - #oauthAuthorizationEndpoint - #oauthTokenEndpoint - #provideClientKey - #signClientKey url = IRI(as2.url) playlists = IRI(pt.playlists) featured = IRI(toot.featured) @@ -542,6 +536,14 @@ class Person(Object): copied_to = IRI(toot.copiedTo) capabilities = Dict(litepub.capabilities) suspended = fields.Boolean(toot.suspended) + # Not implemented yet + #liked is a collection + #streams + #proxyUrl + #oauthAuthorizationEndpoint + #oauthTokenEndpoint + #provideClientKey + #signClientKey @classmethod def from_base(cls, entity): @@ -717,7 +719,7 @@ class Video(Object): for a in act: if type(a) == Person: new_act.append(a.id) - # TODO: fix extract_receivers which can't handle multiple actors! + # TODO: fix extract_receivers which doesn't handle multiple actors! self.actor_id = new_act[0] entity = ActivitypubPost(**self.__dict__) @@ -740,10 +742,10 @@ class Signature(Object): class Activity(Object): actor_id = IRI(as2.actor) - #target_id = IRI(as2.target) + instrument = MixedField(as2.instrument, nested='ServiceSchema') + # Not implemented yet #result #origin - instrument = MixedField(as2.instrument, nested='ServiceSchema') def __init__(self, *args, **kwargs): self.activity = self @@ -868,10 +870,10 @@ def extract_receiver(entity, receiver): return [] + # Work in progress #obj = retrieve_and_parse_document(receiver) #if isinstance(obj, ActivitypubProfile): # return [UserType(id=receiver, receiver_variant=ReceiverVariant.ACTOR)] - #if isinstance(obj, Collection) and base_url: # return process_followers(obj, base_url)