Forwarded activities are believed as far as their origin vouches
A thread's server passes on what happens in it, signed with its own key: Mastodon forwards the replies to its accounts' posts and their deletions, Friendica every activity in its threads. PrivaPub answered them 401, which also tells a sender its signature failed. Now they get 202 and nothing in them is believed: a forwarded Create or Update is taken as its object reads at the actor's origin, a Delete of a public or unlisted copy once that origin answers 404 or 410 (RemoteActorService.IsGone; FederationHttp remembers the status of a refusal), anything else is let go, and our own activities coming back are ignored. A forwarded copy has its own dedupe key, so one that failed never hides the author's own delivery. A reply in the thread of someone followed here is kept, as Mastodon keeps them. Mastodon delivers a reply to the followers of the account it answers; PrivaPub dropped those as unaddressed, which the pasture showed: the outsider's reply its Mastodon scenario said was never delivered had been, and was thrown away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
01808fa644
commit
7eb7c017a5
12 files changed
+334
-21
No files matched your search
@@ -0,0 +1,71 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Federation.Actors;
|
||||
using PrivaPub.Federation.Objects;
|
||||
using PrivaPub.Models.Post;
|
||||
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
using static PrivaPub.Federation.Objects.ActivityJson;
|
||||
|
||||
using PostEntity = PrivaPub.Models.Post.Post;
|
||||
|
||||
namespace PrivaPub.Federation.Inbox
|
||||
{
|
||||
// Inbox forwarding (ActivityPub 7.1.2): a server passes on an activity about a thread it holds to the thread's followers,
|
||||
// signed with its own key. Mastodon forwards the replies to its accounts' posts, and their deletions; Friendica every
|
||||
// activity in its threads. The signature proves who passed it on, never who wrote it, so nothing in it is believed: a
|
||||
// Create or an Update is taken as its object reads at the actor's origin now, a Delete once that origin says the object
|
||||
// is gone, and anything else is let go (a vote or a follow cannot be checked against its origin). An LD signature
|
||||
// (RsaSignature2017) would prove the author, but needs JSON-LD; integrity proofs (FEP-8b32) will.
|
||||
public static class Forwarded
|
||||
{
|
||||
// what may be taken from a forwarder: a post of the activity's actor, created, edited or deleted
|
||||
public static bool Takeable(string type, JsonNode activity, string actorUri)
|
||||
{
|
||||
var objectUri = Id(activity["object"]);
|
||||
return type is "Create" or "Update" or "Delete" && objectUri != default && objectUri != actorUri && Origin.Same(objectUri, actorUri);
|
||||
}
|
||||
|
||||
// the activity as its origin vouches for it, or why it is dropped
|
||||
public static async Task<(JsonNode Activity, string Drop)> Confirm(JsonNode activity, string type, string actorUri,
|
||||
IRemoteActorService remoteActors, CancellationToken token)
|
||||
{
|
||||
var objectUri = Id(activity["object"]);
|
||||
var trusted = new JsonObject
|
||||
{
|
||||
["id"] = Id(activity),
|
||||
["type"] = type,
|
||||
["actor"] = actorUri
|
||||
};
|
||||
if (type == "Delete")
|
||||
{
|
||||
// only a copy anyone may read: the origin answers 404 for a followers-only post to our instance actor too,
|
||||
// and the author's own server tells its recipients of a deletion
|
||||
var held = await DB.Default.Find<PostEntity>().Match(p => p.ObjectURI == objectUri && p.ActorURI == actorUri).ExecuteFirstAsync(token);
|
||||
if (held != default && held.Visibility is not (PostVisibility.Public or PostVisibility.Unlisted))
|
||||
return (default, "forwarded-private");
|
||||
if (!await remoteActors.IsGone(objectUri, token))
|
||||
return (default, "forwarded-not-gone");
|
||||
trusted["object"] = objectUri;
|
||||
return (trusted, default);
|
||||
}
|
||||
|
||||
if (type == "Create")
|
||||
{
|
||||
// the Create handler reads it again from its origin, and records that it did
|
||||
trusted["object"] = objectUri;
|
||||
return (trusted, default);
|
||||
}
|
||||
|
||||
using var fetched = await remoteActors.FetchObject(objectUri, token);
|
||||
if (fetched == default)
|
||||
return (default, "fetch-failed");
|
||||
var node = JsonNode.Parse(fetched.Root.GetRawText());
|
||||
if (!Origin.Same(Id(node), actorUri))
|
||||
return (default, "cross-origin");
|
||||
trusted["object"] = node;
|
||||
return (trusted, default);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user