Files
SocialPub/PrivaPub/Federation/Inbox/Handlers/DeleteHandler.cs
T
thepraandClaude Opus 5.5 2d293a6148 An organiser's edits to a group's event follow its server
Mobilizon's organiser sends the Create, Update and Delete of an event attributed to the group, which announces the
Event itself. PrivaPub refused the organiser's activities as misattributed (400) and kept the event through the
group's Announce, so an edit was lost and a deletion left the event in place. An object attributed to another account
of the actor's own server is now that server's to vouch for: created or edited as the server has it, under the account
it is attributed to, and deleted once the server answers 404 or 410. Attributed to an account elsewhere, it is still
refused. Checked against Mobilizon 5.2.4 in the pasture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
2026-10-05 10:54:32 +02:00

99 lines
3.6 KiB
C#

using MongoDB.Entities;
using PrivaPub.Domain.Statuses;
using PrivaPub.Federation.Actors;
using PrivaPub.Federation.Objects;
using PrivaPub.Federation.Outbox;
using PrivaPub.Federation.Rendering;
using PrivaPub.Models.Federation;
using PrivaPub.Models.Group;
using PrivaPub.Models.Post;
using PrivaPub.Models.Social;
using PrivaPub.Models.User;
using PrivaPub.StaticServices;
using System.Text.Json.Nodes;
using static PrivaPub.Federation.Inbox.ForeignMembers;
using static PrivaPub.Federation.Objects.ActivityJson;
using PostEntity = PrivaPub.Models.Post.Post;
namespace PrivaPub.Federation.Inbox.Handlers
{
public class DeleteHandler : IActivityHandler
{
readonly DbEntities _dbEntities;
readonly ILocalActorService _localActors;
readonly IRemoteActorService _remoteActors;
readonly IDeliveryService _delivery;
readonly IGroupDistributor _groups;
readonly IQuoteService _quotes;
public DeleteHandler(DbEntities dbEntities, ILocalActorService localActors, IRemoteActorService remoteActors, IDeliveryService delivery,
IGroupDistributor groups, IQuoteService quotes)
{
_quotes = quotes;
_groups = groups;
_dbEntities = dbEntities;
_localActors = localActors;
_remoteActors = remoteActors;
_delivery = delivery;
}
public string Type => "Delete";
public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token)
{
var delete = activity;
var objectUri = Id(delete["object"]);
if (!Origin.Same(objectUri, actor.ActorURI))
{
Arrival.Drop("cross-origin");
return;
}
if (objectUri == actor.ActorURI)
{
Arrival.Accept("actor-delete");
Arrival.About(actor.AvatarType.ToString(), created: actor.Published);
actor.DeletionAt = DateTime.UtcNow;
actor.AccountState = AvatarAccountState.Deleted;
await DB.Default.SaveAsync(actor, token);
var followers = await _dbEntities.Followers.Match(f => f.ActorURI == actor.ActorURI).ExecuteAsync(token);
foreach (var follower in followers)
{
await DB.Default.DeleteAsync<Follower>(follower.ID);
if (follower.LocalActorKind == LocalActorKind.Group)
await RemoveForeignMember(follower.LocalActorId, actor.ActorURI, token);
}
await DB.Default.DeleteAsync<Following>(f => f.TargetActorURI == actor.ActorURI);
await DB.Default.DeleteAsync<TimelineEntry>(e => e.AuthorAccountId == actor.ID);
await DB.Default.Update<PostEntity>().Match(p => p.ActorURI == actor.ActorURI).Modify(p => p.AuthorGone, true).ExecuteAsync(token);
await GoneActors.Forget(actor, token);
return;
}
await RemoteDeletes.Tombstone(objectUri, token);
var post = await _dbEntities.Posts.Match(p => p.ObjectURI == objectUri && p.ActorURI == actor.ActorURI).ExecuteFirstAsync(token);
// deleted by another account of its author's server (Mobilizon's organiser deletes the group's event): once
// that server says it is gone
if (post == default && await _dbEntities.Posts.Match(p => p.ObjectURI == objectUri).ExecuteFirstAsync(token) is { } held
&& held.ActorURI != actor.ActorURI && Origin.Same(held.ActorURI, actor.ActorURI) && await _remoteActors.IsGone(objectUri, token))
post = held;
if (post == default)
{
Arrival.Accept("tombstone-only");
await _quotes.Revoke(objectUri, token);
return;
}
Arrival.Accept("removed");
Arrival.About(post.ObjectType, post.Visibility, post.CreationDate);
await RemoteDeletes.Remove(post, objectUri, token);
if (!string.IsNullOrEmpty(post.GroupId) && await _localActors.FindById(LocalActorKind.Group, post.GroupId, token) is { IsCircle: false } community)
await _groups.Announce(community, activity.AsObject(), post.ObjectURI, isNewPost: false, token);
}
}
}