[backend] Handle incoming ASAccept activities

This commit is contained in:
Laura Hausmann 2024-02-06 01:52:26 +01:00
parent 8c9e6ef56c
commit 97e4a25742
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
4 changed files with 57 additions and 6 deletions

View file

@ -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.InstanceSection> 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();
}
}

View file

@ -9,7 +9,7 @@ public class ActivityRenderer(IOptions<Config.InstanceSection> 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.InstanceSection> 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
},

View file

@ -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<ASActivity>;

View file

@ -25,9 +25,9 @@ public class ASObject {
ASNote.Types.Note => token.ToObject<ASNote>(),
ASActivity.Types.Create => token.ToObject<ASActivity>(),
ASActivity.Types.Delete => token.ToObject<ASActivity>(),
ASActivity.Types.Follow => token.ToObject<ASActivity>(),
ASActivity.Types.Unfollow => token.ToObject<ASActivity>(),
ASActivity.Types.Accept => token.ToObject<ASActivity>(),
ASActivity.Types.Follow => token.ToObject<ASFollow>(),
ASActivity.Types.Unfollow => token.ToObject<ASUnfollow>(),
ASActivity.Types.Accept => token.ToObject<ASAccept>(),
ASActivity.Types.Undo => token.ToObject<ASActivity>(),
ASActivity.Types.Like => token.ToObject<ASActivity>(),
_ => token.ToObject<ASObject>()