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
98 lines
3.3 KiB
C#
98 lines
3.3 KiB
C#
using MongoDB.Entities;
|
|
|
|
using PrivaPub.Federation.Actors;
|
|
using PrivaPub.Domain.Statuses;
|
|
using PrivaPub.Federation.Objects;
|
|
using PrivaPub.Federation.Outbox;
|
|
using PrivaPub.Models.Post;
|
|
using PrivaPub.Models.User;
|
|
using PrivaPub.StaticServices;
|
|
|
|
using System.Text.Json.Nodes;
|
|
|
|
using static PrivaPub.Federation.Objects.ActivityJson;
|
|
|
|
using PostEntity = PrivaPub.Models.Post.Post;
|
|
|
|
namespace PrivaPub.Federation.Inbox.Handlers
|
|
{
|
|
public class UpdateHandler : IActivityHandler
|
|
{
|
|
|
|
readonly DbEntities _dbEntities;
|
|
readonly ILocalActorService _localActors;
|
|
readonly IRemoteActorService _remoteActors;
|
|
readonly IGroupDistributor _groups;
|
|
readonly IObjectRecords _records;
|
|
readonly IQuoteService _quotes;
|
|
|
|
public UpdateHandler(DbEntities dbEntities, ILocalActorService localActors, IRemoteActorService remoteActors, IGroupDistributor groups,
|
|
IObjectRecords records, IQuoteService quotes)
|
|
{
|
|
_quotes = quotes;
|
|
_records = records;
|
|
_groups = groups;
|
|
_dbEntities = dbEntities;
|
|
_localActors = localActors;
|
|
_remoteActors = remoteActors;
|
|
}
|
|
|
|
public string Type => "Update";
|
|
|
|
public async Task Handle(JsonNode activity, ForeignAvatar actor, CancellationToken token)
|
|
{
|
|
var inner = activity["object"];
|
|
if (Id(inner) == actor.ActorURI)
|
|
{
|
|
await _remoteActors.GetActor(actor.ActorURI, refresh: true, token);
|
|
Arrival.Accept("actor-refresh");
|
|
Arrival.About(actor.AvatarType.ToString());
|
|
return;
|
|
}
|
|
if (inner is not JsonObject || !Origin.Same(Id(inner), actor.ActorURI))
|
|
{
|
|
Arrival.Drop("cross-origin");
|
|
return;
|
|
}
|
|
var note = NoteParser.Parse(inner);
|
|
// edited by another account of the author's own server (Mobilizon's organiser edits the group's event): applied
|
|
// as that server has it now
|
|
if (note != default && note.AttributedTo != actor.ActorURI && Origin.Same(note.AttributedTo, actor.ActorURI))
|
|
{
|
|
using var fetched = await _remoteActors.FetchObject(note.Id, token);
|
|
var current = fetched == default ? default : NoteParser.Parse(JsonNode.Parse(fetched.Root.GetRawText()));
|
|
if (current == default || current.Id != note.Id || current.AttributedTo != note.AttributedTo)
|
|
{
|
|
Arrival.Drop("fetch-failed");
|
|
return;
|
|
}
|
|
note = current;
|
|
actor = await _remoteActors.GetActor(note.AttributedTo, refresh: false, token);
|
|
if (actor == default)
|
|
{
|
|
Arrival.Drop("author-unavailable");
|
|
return;
|
|
}
|
|
}
|
|
if (note == default || note.AttributedTo != actor.ActorURI)
|
|
{
|
|
Arrival.Drop(note == default ? "unparseable" : "misattributed");
|
|
return;
|
|
}
|
|
|
|
var post = await _dbEntities.Posts.Match(p => p.ObjectURI == note.Id && p.ActorURI == actor.ActorURI).ExecuteFirstAsync(token);
|
|
if (post == default || post.DeletedAt.HasValue)
|
|
{
|
|
Arrival.Drop("unknown-object");
|
|
return;
|
|
}
|
|
Arrival.About(note.Type, post.Visibility, post.CreationDate);
|
|
|
|
var edited = await RemoteEdits.Apply(post, note, Id(activity), _localActors, _records, _quotes, token);
|
|
Arrival.Accept(edited ? "edit" : "refresh");
|
|
if (edited && !string.IsNullOrEmpty(post.GroupId) && await _localActors.FindById(Models.Federation.LocalActorKind.Group, post.GroupId, token) is { IsCircle: false } community)
|
|
await _groups.Announce(community, activity.AsObject(), post.ObjectURI, isNewPost: false, token);
|
|
}
|
|
}
|
|
}
|