The inbox answers once it has verified, and processes from the job queue
InboxService is split the way the roadmap lays out Federation/Inbox:
- InboxReceiver reads and verifies the request exactly as before, runs
the checks that need no fetch (the activity id's origin, a Follow of a
missing or local-only actor, an Undo of someone else's activity, an
embedded object attributed to someone else), queues a ProcessInbox job
and answers 202. The job's dedupe key is the activity id, so a peer that
delivers the same activity twice is processed once.
- InboxProcessor (two at a time, eight attempts) loads the verified actor
and hands the activity to the handler for its type.
- Handlers/{Follow,Undo,Create,Delete,Update}Handler are the old methods,
unchanged except that they no longer produce status codes; the JSON
helpers live in Objects/ActivityJson and the group membership helpers in
Inbox/ForeignMembers.
A slow fetch of an object or a remote actor now delays the job, not the
sender's HTTP request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
d1a91c40c4
commit
796f06acf4
13 files changed
+755
-430
No files matched your search
@@ -5,6 +5,8 @@ using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Federation.Actors;
|
||||
using PrivaPub.Federation.Inbox;
|
||||
using PrivaPub.Federation.Inbox.Handlers;
|
||||
using PrivaPub.Models.Jobs;
|
||||
using PrivaPub.Federation.Outbox;
|
||||
using PrivaPub.Infrastructure.Jobs;
|
||||
using PrivaPub.Models;
|
||||
@@ -28,7 +30,8 @@ namespace PrivaPub.Tests.Federation
|
||||
|
||||
Peer _peer;
|
||||
LocalActorService _local;
|
||||
InboxService _inbox;
|
||||
InboxReceiver _receiver;
|
||||
InboxProcessor _processor;
|
||||
|
||||
public async ValueTask InitializeAsync()
|
||||
{
|
||||
@@ -37,7 +40,18 @@ namespace PrivaPub.Tests.Federation
|
||||
var cache = new MemoryCache(new MemoryCacheOptions());
|
||||
_local = new LocalActorService(new DbEntities(), new StaticOptions<AppConfiguration>(new AppConfiguration { BackendBaseAddress = Base }));
|
||||
var remote = new RemoteActorService(Peer.Http(cache), _local, cache, new DbEntities());
|
||||
_inbox = new InboxService(new DbEntities(), _local, remote, new DeliveryService(new DbEntities(), new JobQueue()), NullLogger<InboxService>.Instance);
|
||||
var queue = new JobQueue();
|
||||
var delivery = new DeliveryService(new DbEntities(), queue);
|
||||
var db = new DbEntities();
|
||||
_receiver = new InboxReceiver(_local, remote, queue, NullLogger<InboxReceiver>.Instance);
|
||||
_processor = new InboxProcessor(remote, new IActivityHandler[]
|
||||
{
|
||||
new FollowHandler(db, _local, remote, delivery),
|
||||
new UndoHandler(db, _local, remote, delivery),
|
||||
new CreateHandler(db, _local, remote, delivery),
|
||||
new DeleteHandler(db, _local, remote, delivery),
|
||||
new UpdateHandler(db, _local, remote, delivery)
|
||||
}, NullLogger<InboxProcessor>.Instance);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
@@ -78,6 +92,17 @@ namespace PrivaPub.Tests.Federation
|
||||
};
|
||||
}
|
||||
|
||||
async Task<InboxResult> Deliver(RemoteActor sender, string path, JsonNode activity)
|
||||
{
|
||||
var token = TestContext.Current.CancellationToken;
|
||||
var result = await _receiver.Receive(sender.Post(Host, path, activity), default, token);
|
||||
var dedupe = "inbox|" + (activity is JsonObject ? activity["id"]?.GetValue<string>() : default);
|
||||
var job = await DB.Default.Find<Job>().Match(j => j.DedupeKey == dedupe).ExecuteFirstAsync(token);
|
||||
if (job != default)
|
||||
Assert.Equal(JobResult.Done, (await _processor.Handle(job, token)).Result);
|
||||
return result;
|
||||
}
|
||||
|
||||
static string Origin(string uri) => new Uri(uri).GetLeftPart(UriPartial.Authority);
|
||||
|
||||
[Fact]
|
||||
@@ -89,8 +114,8 @@ namespace PrivaPub.Tests.Federation
|
||||
var mallory = new RemoteActor(_peer, "mallory");
|
||||
var context = $"{_peer.A}/contexts/{Guid.NewGuid():N}";
|
||||
|
||||
var first = await _inbox.Receive(bob.Post(Host, $"/peasants/{alice.UserName}/mouth", DirectCreate(bob, alice.Uri, context)), alice, token);
|
||||
var injected = await _inbox.Receive(mallory.Post(Host, $"/peasants/{alice.UserName}/mouth", DirectCreate(mallory, alice.Uri, context)), alice, token);
|
||||
var first = await Deliver(bob, $"/peasants/{alice.UserName}/mouth", DirectCreate(bob, alice.Uri, context));
|
||||
var injected = await Deliver(mallory, $"/peasants/{alice.UserName}/mouth", DirectCreate(mallory, alice.Uri, context));
|
||||
|
||||
Assert.Equal(202, first.StatusCode);
|
||||
Assert.Equal(202, injected.StatusCode);
|
||||
@@ -108,8 +133,8 @@ namespace PrivaPub.Tests.Federation
|
||||
var alice = await LocalAvatar("alice");
|
||||
var bob = new RemoteActor(_peer, "bob");
|
||||
|
||||
await _inbox.Receive(bob.Post(Host, $"/peasants/{alice.UserName}/mouth", DirectCreate(bob, alice.Uri)), alice, token);
|
||||
await _inbox.Receive(bob.Post(Host, $"/peasants/{alice.UserName}/mouth", DirectCreate(bob, alice.Uri)), alice, token);
|
||||
await Deliver(bob, $"/peasants/{alice.UserName}/mouth", DirectCreate(bob, alice.Uri));
|
||||
await Deliver(bob, $"/peasants/{alice.UserName}/mouth", DirectCreate(bob, alice.Uri));
|
||||
|
||||
var dms = await DB.Default.Find<DmPost>().Match(p => p.ActorURI == bob.Id).ExecuteAsync(token);
|
||||
Assert.Equal(2, dms.Count);
|
||||
@@ -125,7 +150,7 @@ namespace PrivaPub.Tests.Federation
|
||||
var create = DirectCreate(mallory, alice.Uri);
|
||||
create["id"] = $"{_peer.B}/activities/{Guid.NewGuid():N}";
|
||||
|
||||
var result = await _inbox.Receive(mallory.Post(Host, $"/peasants/{alice.UserName}/mouth", create), alice, token);
|
||||
var result = await Deliver(mallory, $"/peasants/{alice.UserName}/mouth", create);
|
||||
|
||||
Assert.Equal(400, result.StatusCode);
|
||||
}
|
||||
@@ -138,8 +163,7 @@ namespace PrivaPub.Tests.Federation
|
||||
var mallory = new RemoteActor(_peer, "mallory");
|
||||
var victim = new RemoteActor(_peer, "victim");
|
||||
|
||||
var result = await _inbox.Receive(mallory.Post(Host, $"/peasants/{alice.UserName}/mouth",
|
||||
DirectCreate(mallory, alice.Uri, attributedTo: victim.Id)), alice, token);
|
||||
var result = await Deliver(mallory, $"/peasants/{alice.UserName}/mouth", DirectCreate(mallory, alice.Uri, attributedTo: victim.Id));
|
||||
|
||||
Assert.Equal(400, result.StatusCode);
|
||||
Assert.False(await DB.Default.Find<DmPost>().Match(p => p.ActorURI == victim.Id).ExecuteAnyAsync(token));
|
||||
@@ -152,8 +176,7 @@ namespace PrivaPub.Tests.Federation
|
||||
var alice = await LocalAvatar("alice");
|
||||
var mallory = new RemoteActor(_peer, "mallory");
|
||||
|
||||
var result = await _inbox.Receive(mallory.Post(Host, $"/peasants/{alice.UserName}/mouth",
|
||||
DirectCreate(mallory, alice.Uri, objectOrigin: _peer.B)), alice, token);
|
||||
var result = await Deliver(mallory, $"/peasants/{alice.UserName}/mouth", DirectCreate(mallory, alice.Uri, objectOrigin: _peer.B));
|
||||
|
||||
Assert.Equal(202, result.StatusCode);
|
||||
Assert.False(await DB.Default.Find<DmPost>().Match(p => p.ActorURI == mallory.Id).ExecuteAnyAsync(token));
|
||||
@@ -168,7 +191,7 @@ namespace PrivaPub.Tests.Federation
|
||||
var request = mallory.Post(Host, $"/peasants/{alice.UserName}/mouth", DirectCreate(mallory, alice.Uri));
|
||||
request.Headers["Signature"] = request.Headers["Signature"].ToString().Replace("signature=\"", "signature=\"AAAA");
|
||||
|
||||
Assert.Equal(401, (await _inbox.Receive(request, alice, token)).StatusCode);
|
||||
Assert.Equal(401, (await _receiver.Receive(request, alice, token)).StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -179,7 +202,7 @@ namespace PrivaPub.Tests.Federation
|
||||
var mallory = new RemoteActor(_peer, "mallory");
|
||||
|
||||
foreach (var junk in new JsonNode[] { new JsonArray(1, 2), JsonValue.Create("x"), new JsonObject { ["type"] = "Create" } })
|
||||
Assert.Equal(400, (await _inbox.Receive(mallory.Post(Host, $"/peasants/{alice.UserName}/mouth", junk), alice, token)).StatusCode);
|
||||
Assert.Equal(400, (await Deliver(mallory, $"/peasants/{alice.UserName}/mouth", junk)).StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -200,7 +223,7 @@ namespace PrivaPub.Tests.Federation
|
||||
};
|
||||
|
||||
Assert.False(actor.IsFederated);
|
||||
Assert.Equal(404, (await _inbox.Receive(bob.Post(Host, "/human-centipede", follow), default, token)).StatusCode);
|
||||
Assert.Equal(404, (await Deliver(bob, "/human-centipede", follow)).StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user