From 97e4a25742f4dc60cd5ff8b0b18f10a3f9639d12 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Tue, 6 Feb 2024 01:52:26 +0100 Subject: [PATCH] [backend] Handle incoming ASAccept activities --- .../ActivityPub/ActivityHandlerService.cs | 45 ++++++++++++++++++- .../ActivityPub/ActivityRenderer.cs | 4 +- .../ActivityStreams/Types/ASActivity.cs | 8 ++++ .../ActivityStreams/Types/ASObject.cs | 6 +-- 4 files changed, 57 insertions(+), 6 deletions(-) diff --git a/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityHandlerService.cs b/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityHandlerService.cs index fcd290fd..4b367cfa 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityHandlerService.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityHandlerService.cs @@ -1,3 +1,4 @@ +using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Federation.ActivityStreams; @@ -7,6 +8,7 @@ using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Queues; using Iceshrimp.Backend.Core.Services; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; namespace Iceshrimp.Backend.Core.Federation.ActivityPub; @@ -16,7 +18,8 @@ public class ActivityHandlerService( UserResolver userResolver, DatabaseContext db, QueueService queueService, - ActivityRenderer activityRenderer + ActivityRenderer activityRenderer, + IOptions config ) { public Task PerformActivityAsync(ASActivity activity, string? inboxUserId) { logger.LogDebug("Processing activity: {activity}", activity.Id); @@ -38,6 +41,10 @@ public class ActivityHandlerService( if (activity.Object is { } obj) return UnfollowAsync(obj, activity.Actor); throw GracefulException.UnprocessableEntity("Unfollow activity object is invalid"); } + case ASActivity.Types.Accept: { + if (activity.Object is { } obj) return AcceptAsync(obj, activity.Actor); + throw GracefulException.UnprocessableEntity("Follow activity object is invalid"); + } case ASActivity.Types.Undo: { //TODO: implement the rest //TODO: test if this actually works @@ -118,4 +125,40 @@ public class ActivityHandlerService( await db.FollowRequests.Where(p => p.Follower == follower && p.Followee == followee).ExecuteDeleteAsync(); await db.Followings.Where(p => p.Follower == follower && p.Followee == followee).ExecuteDeleteAsync(); } + + private async Task AcceptAsync(ASObject obj, ASObject actor) { + var prefix = $"https://{config.Value.WebDomain}/follows/"; + if (!obj.Id.StartsWith(prefix)) + throw GracefulException.UnprocessableEntity($"Object id '{obj.Id}' not a valid follow request"); + + var resolvedActor = await userResolver.ResolveAsync(actor.Id); + var ids = obj.Id[prefix.Length..].TrimEnd('/').Split("/"); + if (ids.Length != 2 || ids[1] != resolvedActor.Id) + throw GracefulException + .UnprocessableEntity($"Actor id '{resolvedActor.Id}' doesn't match followee id '{ids[1]}'"); + + var request = await db.FollowRequests + .Include(p => p.Follower) + .FirstOrDefaultAsync(p => p.Followee == resolvedActor && p.FollowerId == ids[0]); + + if (request == null) + throw GracefulException + .UnprocessableEntity($"No follow request matching follower '{ids[0]}' and followee '{resolvedActor.Id}' found"); + + var following = new Following { + Id = IdHelpers.GenerateSlowflakeId(), + CreatedAt = DateTime.UtcNow, + Follower = request.Follower, + Followee = resolvedActor, + FollowerHost = request.FollowerHost, + FolloweeHost = request.FolloweeHost, + FollowerInbox = request.FollowerInbox, + FolloweeInbox = request.FolloweeInbox, + FollowerSharedInbox = request.FollowerSharedInbox, + FolloweeSharedInbox = request.FolloweeSharedInbox + }; + db.Remove(request); + await db.AddAsync(following); + await db.SaveChangesAsync(); + } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityRenderer.cs b/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityRenderer.cs index 5098f364..69a708fb 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityRenderer.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityPub/ActivityRenderer.cs @@ -9,7 +9,7 @@ public class ActivityRenderer(IOptions config) { public static ASActivity RenderCreate(ASObject obj, ASObject actor) { return new ASActivity { Id = $"{obj.Id}#Create", - Type = "https://www.w3.org/ns/activitystreams#Create", + Type = ASActivity.Types.Create, Actor = new ASActor { Id = actor.Id }, Object = obj }; @@ -18,7 +18,7 @@ public class ActivityRenderer(IOptions config) { public ASActivity RenderAccept(ASObject actor, ASObject obj) { return new ASActivity { Id = $"https://{config.Value.WebDomain}/activities/{Guid.NewGuid().ToString().ToLowerInvariant()}", - Type = "https://www.w3.org/ns/activitystreams#Accept", + Type = ASActivity.Types.Accept, Actor = new ASActor { Id = actor.Id }, diff --git a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASActivity.cs b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASActivity.cs index 4cfe1a22..62fd4144 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASActivity.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASActivity.cs @@ -30,6 +30,14 @@ public class ASFollow : ASActivity { public ASFollow() => Type = Types.Follow; } +public class ASUnfollow : ASActivity { + public ASUnfollow() => Type = Types.Unfollow; +} + +public class ASAccept : ASActivity { + public ASAccept() => Type = Types.Accept; +} + //TODO: add the rest public sealed class ASActivityConverter : ASSerializer.ListSingleObjectConverter; \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASObject.cs b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASObject.cs index 80d24d7b..3e418837 100644 --- a/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASObject.cs +++ b/Iceshrimp.Backend/Core/Federation/ActivityStreams/Types/ASObject.cs @@ -25,9 +25,9 @@ public class ASObject { ASNote.Types.Note => token.ToObject(), ASActivity.Types.Create => token.ToObject(), ASActivity.Types.Delete => token.ToObject(), - ASActivity.Types.Follow => token.ToObject(), - ASActivity.Types.Unfollow => token.ToObject(), - ASActivity.Types.Accept => token.ToObject(), + ASActivity.Types.Follow => token.ToObject(), + ASActivity.Types.Unfollow => token.ToObject(), + ASActivity.Types.Accept => token.ToObject(), ASActivity.Types.Undo => token.ToObject(), ASActivity.Types.Like => token.ToObject(), _ => token.ToObject()