FEP-400e: Publicly-appendable ActivityPub collections

source: https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-400e.md


authors: @grishka
status: FINAL
dateReceived: 2021-02-16
dateFinalized: 2022-02-04

FEP-400e: Publicly-appendable ActivityPub collections

Summary

In social media, it’s a frequent pattern when there’s a collection owned by someone that other people can contribute to. Examples include:

  • Walls where others can post
  • Forums as well as topics within
  • Photo albums in groups where group members can add photos

Currently, there is no generic way to signify that an object was created as part of a collection and should only be considered in its context.

This proposal describes how ActivityPub servers and clients could specify collections to which objects created by their actors belong.

Requirements

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this specification are to be interpreted as described in [RFC-2119].

Publicly-appendable collections

A publicly-appendable collection is any collection where it is expected that someone other than its owner could add items but over which its owner retains complete authority. For example, a wall is a collection to which other people could add posts (Notes), but from which its owner could delete any posts as well as restrict who can add them.

A publicly-appendable collection SHOULD have a valid and globally-unique id that SHOULD point to either a Collection or an OrderedCollection object that contains the links to all its objects.

Specifying collections in actors

If an actor has publicly-appendable collections, its server MAY include them as additional fields in its ActivityPub representation. For example, user actors may specify the link to their walls, or groups may specify the link to the collection of their photo albums.

Implementations MAY use the presence or absence of specific collection to determine whether the actor’s server supports features that depend on that collection and alter their UIs accordingly.

Using target in objects

If an ActivityPub object is being created as part of a collection, the object SHOULD include the target field that contains an abbreviated collection object, which SHOULD contain at least the following fields:

  • type — either Collection or OrderedCollection.
  • id — the id of the collection.
  • attributedTo — the id of the owner of the collection. This is necessary to simplify the database design on the receiving side.

Discussion

While [Activity Vocabulary] specifies target as a field with similar semantics in activities, it’s important to include it in objects themselves so any software that only sees the object without its enclosing Create activity, e.g. when following a link form another object or retrieving the object from a user-provided URI, unambiguously knows that it should only be considered in the context of its collection.

Adding an object to a collection

When an ActivityPub server receives in its inbox a correctly signed Create activity with an object that has the target field, it does the following:

  • Retrieve the collection owner using either attributedTo or id fields of the abbreviated collection object.
    • If the collection owner does not exist, or if attributedTo doesn’t match the actual owner of the collection specified by id, or if the collection owner is not a local actor, the server SHOULD abort processing and MAY return 400 Bad Request.
    • If the object could not be added to the collection, for example due to the privacy settings configured by its owner, the server SHOULD either respond with 403 Unauthorized or respond with 200 OK and later send a Reject{Create} activity to the originating server.
  • Store either the entire object or its id in its local storage as belonging to the specified collection.
  • Send an Add activity to any parties that might be concerned with it. The target field in the activity SHOULD only be the collection id, and the object field SHOULD be the id of the object being added. It is RECOMMENDED that this activity is sent to all the collection owner’s followers for the sake of data consistency, and it SHOULD be sent to the actor that created the object being added.
  • Perform any implementation-specific processing, like sending notifications.

Receiving an Add activity

When an ActivityPub server receives in its inbox a correctly signed Add activity, it SHOULD do the following:

  • Retrieve the actor either from local storage or from the network.
  • Retrieve the object.
  • Check that target is the ID of a collection owned by actor. If it is not, abort processing and return 400 Bad Request.
  • Check that target in the activity matches target.id in the object and that target.attributedTo in the object matches the actor ID. If it does not, abort processing and return 400 Bad Request.
  • Store either the entire object or its id in its local storage as belonging to the specified collection.
  • Perform any implementation-specific processing, like sending notifications.

Example of an Add activity

{
  "@context":"https://www.w3.org/ns/activitystreams",
  "actor":"https://example.com/users/1",
  "id":"https://example.com/posts/41864/activityAdd",
  "to":[
    "https://www.w3.org/ns/activitystreams#Public",
    "https://example.com/users/1/followers",
    "https://example.com/users/6946"
  ],
  "type":"Add",
  "object":"https://example.com/posts/41864",
  "target":"https://example.com/users/1/wall"
}

Deleting an object from a collection

Since the collection owner has complete authority over the contents of the collection, they can delete any objects from it. When an object is deleted from a collection by its owner, their server SHOULD send a Delete activity to at least the server of the actor that created the object; it’s also RECOMMENDED that this activity is sent to all the servers that Add was sent to. Those servers then SHOULD delete the object as if the deletion was initiated by its creator.

Moving an object between collections

In some use cases, it might make sense to allow objects to be moved between collections, for example, a group moderator might want to move a photo between photo albums in a group, or a forum moderator might want to split some messages into a separate thread. It’s only possible to move objects between collections that are owned by the same actor.

When moving an object between collections, the collection owner SHOULD send a Move activity to at least the server of the object creator, specifying the target collection and the id of the object; it’s also RECOMMENDED that this activity is sent to all the servers that Add was sent to. Those servers then SHOULD update the target field in their stored copies of the object.

Security considerations

The requirement of an Add activity sent by a collection owner largely prevents a bad actor from effectively adding something to a collection against the collection owner’s will while also helping data consistency across servers. However, there’s still one case when this is possible. When a server fetches an object that has a target field directly, for example when a user has entered its URL into a search box or when it is referenced by a field such as inReplyTo, there’s presently no reliable way to verify whether the object actually belongs to the collection.

Implementations

This proposal is implemented in Smithereen for both user and group walls since the following commit: Imagine a world where I pay attention to my code 🙈 · grishka/Smithereen@de01359 · GitHub

References

Copyright

CC0 1.0 Universal (CC0 1.0) Public Domain Dedication

To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.

1 Like

To readers of this topic, please note FYI that in Groups implementation - #70 by macgirvin and onwards @macgirvin and @grishka are discussing subjects that are related to this FEP.

(@how maybe you can reassign this topic to @grishka as he is the one assigned to the FEP)

Update:

I applaud @macgirvin’s objective to align with the FEP and provide feedback on it, just like I applaud @grishka having taken the time and effort to write it. An opportunity to see the FEP process (which had stalled somewhat) in action. In that light I suggest having discussion here, so that we practice, familiarize ourselves with, and are able to refine the process. Maybe a relevant summary might be added below?

@grishka FYI I just re-read a post on Feneas forum by @fr33domlover which appears to be describing a publicly-appendable AP collection mechanism:

Yes, when I said that such a pattern is needed in all kinds of social applications, I meant it :smirk:

Though now that @macgirvin has pointed out some security/trust issues with this FEP as it is, it needs some more work before it could be widely implemented.

Uhm, not sure where that came from, but I have no security/trust issues with the FEP as is, and have implemented it as described in the FEP pursuant to the clarifications that were provided. I’m happy with it, it works with our trust model, and currently allows us to post to any (*) existing groups implementation in the fediverse with a single carefully-crafted activity. I wouldn’t recommend changing it; because that might destroy this accidental but amazing opportunity for groups compatibility.

But I’ll leave the decision to you.

(*) Except Mobilizon which I believe only allows selected local logins to initiate topics.

It’s not as much about security as it is about the abuse potential.

Suppose someone was spamming a group with wall posts. Group admins blocked either them or their entire instance. But then that person started sending Create{Note}'s directly to group participants that are on instances other than the group — and, because their instances have no idea what is blocked by the group actor, those activities go through just fine, and the posts appear on the wall, with group admins having no sensible recourse. Yes, they could delete them, that would work. But that’d be a poor workaround for a problem that could be avoided entirely. Using Add{Note} would also not require the support for LD-signatures, thus lowering the barrier to entry.

I’ll investigate but I’m pretty certain that exploit wouldn’t work here. Our relationship and permissions aren’t with the actor but with the sender.

Anyway do what you need to do. When we connect with a group we send follow/group as well as join/group in the absence of any guidance to the contrary. Messy but necessary. Was just trying to avoid sending two activities for every group post as well when we don’t know the group architecture. Perhaps that can still be accomplished.

So how would new wall posts be propagated to group members then? What would happen if a user on instance A sends a wall post to a group on instance B, how would that post reach another user on instance C? Some form of forwarding needs to happen anyway, be it forwarding the original (LD-signed) Create{Note} or, as I’m now suggesting, an Add{Note} generated by the group actor.

Again, these are not issues our implementation is concerned with because our architecture transcends ActivityPub. Most of the issues we are currently working on have analogies and solutions in the history/evolution of email protocols, which I was intimately involved in for a number of decades. We don’t have all the problems you might think we do because we are able to draw on this wealth of messaging experience as well as lessons learned trying to federate over the complexities and limitations and hard-wired policy assumptions of earlier fediverse protocols.

I don’t wish to be drawn into another debate about petty platform differences. Those aren’t relevant to the discussion. We’re discussing FEP-400e. I’ve provided feedback and have nothing more to add.

I’m asking all this because I’m seriously thinking about updating the FEP with it. Acknowledgements such as Add{Note} are important as an integral part of the flow in the same sense that activities such as Follow require an acknowledgement in the form of an Accept{Follow} and thus can’t be considered in isolation from this requirement.

Can you ignore them on receipt and unconditionally generate them for any incoming wall posts? Absolutely. But it’s nice to have this feedback mechanism specified in case it’s needed. After all, a publicly-appendable collection is a deliberately generic construct, so there might be cases where someone would manually approve the addition of new items to a collection for example. In the same sense that I send an Accept{Follow} unconditionally, but Mastodon has a setting that allows the user to manually review and approve follow requests.

The “query whether a collection contains an element” should indeed be a separate FEP and as of now I have no idea how to best approach that. This kind of interaction sort of falls outside of the whole ActivityPub paradigm with its actors and objects.

I’ve submitted a pull request to update the FEP.

1 Like

I see a number of issues arising because Create/Note/Collection simply isn’t going to be on the radar of projects implementing simple microblogs. I believe most existing projects today will simply publish it (subject to addressing). They aren’t even thinking of targets and will likely just post it as a Create/Note activity.

I think that Offer/Note/Collection would make a much better construct in this case because it implies the presence of a target and is therefore much less likely to be mis-handled or accidentally published by the wrong entity. This also has a beneficial aspect - it gives the recipient an opportunity to Accept or Reject it, although I probably wouldn’t make that a hard requirement (we typically don’t waste resources responding to spammers). Acceptance could be implied if the note ends up in the collection and is re-delivered to that collection’s audience.

But you aren’t meant to send this activity to servers that don’t understand it (i.e. don’t have the wall collection). To post on someone’s wall, you don’t send that Create{Note} to your followers like you normally would, you instead send it to the wall owner’s server only. It then sends Add{Note} to the wall owner’s followers. Mastodon doesn’t know what to make of it and simply ignores it, I tested.

Understood, I thought it might avoid issues down the road by requesting acceptance from the collection rather than implying it, as this makes more sense for human moderated collections and would be more difficult to add in later, rather than building in that ability from the beginning. No worry.

It’s not implied, that’s literally what I’ve written in the updated FEP :wink:

It’s a SHOULD even.

So we might support it outbound, but I can’t use it inbound, as we have human moderation mechanisms to integrate and the FEP doesn’t really provide for delayed decisions - beyond moving a post after it’s already caused damage and once the moderators wake up. Again, no worry.

What lead you to this conclusion? It absolutely does. You can delay the Add as much as you want, there’s no requirement that it’s sent instantly, and you can totally send a Delete or a Reject instead.

Indeed it appears that it does.

  • If the object could not be added to the collection, for example due to the privacy settings configured by its owner, the server SHOULD either respond with 403 Unauthorized or respond with 200 OK and later send a Reject{Create} activity to the originating server.

Okay here are my comments on the FEP:

Publicly-appendable collections:

either a Collection or an OrderedCollection object that contains the links to all its objects

Why does it have to contain links to the objects? Thats quite inefficient, as you will have to fetch each object one by one. Its more efficient to have objects directly embedded in the collection json. So this should probably be left for each implementation to decide.

Specifying collections in actors

So it would look something like this? Its a good idea. Would be good to include an example in the FEP.

"wall": "https://friends.grishka.me/grishka/wall",
"albums": "https://friends.grishka.me/grishka",

Using target in objects

Why is this SHOULD and not MUST? If the target field is not set, how do other instances know which collection it belongs to?

And I dont really see why its necessary to include the type or attributedTo fields. In Lemmy we would just do a db query with the collection id to get that other info, so it doesnt really simplify anything. Maybe its useful for Smithereen, shouldnt be part of a standard that many different projects will use.

And the target field is also required in activities, right? Sure in case of a Create/Note you can get the collection id from the Note, but there are many activities in Lemmy which dont have an object (eg Like). This is mentioned in the “discussion”, but seems important enough to make a general section “target field”, which applies to both activities and objects in a collection.

Adding an object to a collection

This seems more like an example than part of the spec. I would rename it as such, and move it to the end, also with some example json as mentioned above.


One thing I would definitely like you to add is a description of the HTTP signature based activity forwarding that you mentioned in the other thread. I know HTTP sigs are not part of the Activitypub standard, but this seems like the best place to document it. And so far i’m not really sure how it works.

Lemmy also has many other actions, but all of them work in the same way (user sends to community, community announces to followers). So you could describe the forwarding in a more generic way (with the http signatures), and have the part about deleting as another example.

1 Like

Because you most probably don’t store the original json, and also for security reasons so the origin server retains its part of authority over the object. You can embed your own objects I think? But the ones from other servers have to be links. Mastodon does this with e.g. replies.

:thinking::thinking::thinking::thinking:

So you could avoid having a zillion indexes in your database, and also could use in-memory cache, and so on. It’s much easier to first query the actor and then find which collection of that actor it is than to have this many different ways to reference it. Also, imagine an actor having 5 different collections, which field do you query on then? Or do you have to have a separate table that maps collection IDs to actors? It does say “simplify database design” for a reason :wink:

No, why? And why would you want that in Like anyway?

I don’t support it (yet?) in Smithereen, but the basic idea is that you would forward an activity, re-signing it with whatever actor you want, and the receiving server would re-fetch it from the origin. This obviously requires the activity to have a unique, resolvable id.