[backend/federation] Make sure new follow & unfollow activities have a consistent identifier (ISH-367)

This commit is contained in:
Laura Hausmann 2024-06-17 17:11:45 +02:00
parent 1f29ad99c7
commit fc7a1fe95c
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
6 changed files with 85 additions and 19 deletions

View file

@ -0,0 +1,42 @@
using System;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Iceshrimp.Backend.Core.Database.Migrations
{
/// <inheritdoc />
[DbContext(typeof(DatabaseContext))]
[Migration("20240617151122_AddFollowRelationshipIdColumns")]
public partial class AddFollowRelationshipIdColumns : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Guid>(
name: "relationshipId",
table: "following",
type: "uuid",
nullable: true);
migrationBuilder.AddColumn<Guid>(
name: "relationshipId",
table: "follow_request",
type: "uuid",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "relationshipId",
table: "following");
migrationBuilder.DropColumn(
name: "relationshipId",
table: "follow_request");
}
}
}

View file

@ -1158,6 +1158,10 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
.HasColumnName("followerSharedInbox")
.HasComment("[Denormalized]");
b.Property<Guid?>("RelationshipId")
.HasColumnType("uuid")
.HasColumnName("relationshipId");
b.Property<string>("RequestId")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
@ -1238,6 +1242,10 @@ namespace Iceshrimp.Backend.Core.Database.Migrations
.HasColumnName("followerSharedInbox")
.HasComment("[Denormalized]");
b.Property<Guid?>("RelationshipId")
.HasColumnType("uuid")
.HasColumnName("relationshipId");
b.HasKey("Id");
b.HasIndex("CreatedAt");

View file

@ -36,6 +36,9 @@ public class FollowRequest : IEntity
[Column("requestId")]
[StringLength(128)]
public string? RequestId { get; set; }
[Column("relationshipId")]
public Guid? RelationshipId { get; set; }
/// <summary>
/// [Denormalized]

View file

@ -79,6 +79,9 @@ public class Following
[Column("followeeSharedInbox")]
[StringLength(512)]
public string? FolloweeSharedInbox { get; set; }
[Column("relationshipId")]
public Guid? RelationshipId { get; set; }
[ForeignKey(nameof(FolloweeId))]
[InverseProperty(nameof(User.IncomingFollowRelationships))]

View file

@ -109,7 +109,7 @@ public class ActivityRenderer(
return res;
}
public ASFollow RenderFollow(User follower, User followee)
public ASFollow RenderFollow(User follower, User followee, Guid? relationshipId)
{
if (follower.Host == null && followee.Host == null)
throw GracefulException.BadRequest("Refusing to render follow activity between two remote users");
@ -118,10 +118,10 @@ public class ActivityRenderer(
return RenderFollow(userRenderer.RenderLite(follower),
userRenderer.RenderLite(followee),
RenderFollowId(follower, followee));
RenderFollowId(follower, followee, relationshipId));
}
public ASActivity RenderUnfollow(User follower, User followee)
public ASActivity RenderUnfollow(User follower, User followee, Guid? relationshipId)
{
if (follower.Host == null && followee.Host == null)
throw GracefulException.BadRequest("Refusing to render unfollow activity between two remote users");
@ -132,13 +132,13 @@ public class ActivityRenderer(
{
var actor = userRenderer.RenderLite(follower);
var obj = userRenderer.RenderLite(followee);
return RenderUndo(actor, RenderFollow(actor, obj, RenderFollowId(follower, followee)));
return RenderUndo(actor, RenderFollow(actor, obj, RenderFollowId(follower, followee, relationshipId)));
}
else
{
var actor = userRenderer.RenderLite(followee);
var obj = userRenderer.RenderLite(follower);
return RenderReject(actor, RenderFollow(actor, obj, RenderFollowId(follower, followee)));
return RenderReject(actor, RenderFollow(actor, obj, RenderFollowId(follower, followee, relationshipId)));
}
}
@ -178,8 +178,8 @@ public class ActivityRenderer(
};
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter", Justification = "This only makes sense for users")]
private string RenderFollowId(User follower, User followee) =>
$"https://{config.Value.WebDomain}/follows/{follower.Id}/{followee.Id}/{Guid.NewGuid().ToStringLower()}";
private string RenderFollowId(User follower, User followee, Guid? relationshipId) =>
$"https://{config.Value.WebDomain}/follows/{follower.Id}/{followee.Id}/{(relationshipId ?? Guid.NewGuid()).ToStringLower()}";
public static ASAnnounce RenderAnnounce(
ASNote note, ASActor actor, List<ASObjectBase> to, List<ASObjectBase> cc, string uri

View file

@ -561,7 +561,8 @@ public class UserService(
FollowerInbox = request.FollowerInbox,
FolloweeInbox = request.FolloweeInbox,
FollowerSharedInbox = request.FollowerSharedInbox,
FolloweeSharedInbox = request.FolloweeSharedInbox
FolloweeSharedInbox = request.FolloweeSharedInbox,
RelationshipId = request.RelationshipId
};
await db.Users.Where(p => p.Id == request.Follower.Id)
@ -645,13 +646,16 @@ public class UserService(
if (follower.Host != null && followee.Host != null)
throw GracefulException.UnprocessableEntity("Cannot process follow between two remote users");
if (followee.Host != null)
Guid? relationshipId = null;
if (followee.IsRemoteUser)
{
var activity = activityRenderer.RenderFollow(follower, followee);
relationshipId = Guid.NewGuid();
var activity = activityRenderer.RenderFollow(follower, followee, relationshipId);
await deliverSvc.DeliverToAsync(activity, follower, followee);
}
if (follower.Host != null)
if (follower.IsRemoteUser)
{
if (requestId == null)
throw GracefulException.UnprocessableEntity("Cannot process remote follow without requestId");
@ -688,7 +692,8 @@ public class UserService(
FolloweeInbox = followee.Inbox,
FollowerInbox = follower.Inbox,
FolloweeSharedInbox = followee.SharedInbox,
FollowerSharedInbox = follower.SharedInbox
FollowerSharedInbox = follower.SharedInbox,
RelationshipId = relationshipId
};
await db.AddAsync(request);
@ -764,13 +769,18 @@ public class UserService(
/// </remarks>
public async Task UnfollowUserAsync(User user, User followee)
{
if ((followee.PrecomputedIsFollowedBy ?? false) || (followee.PrecomputedIsRequestedBy ?? false))
if (((followee.PrecomputedIsFollowedBy ?? false) || (followee.PrecomputedIsRequestedBy ?? false)) &&
followee.IsRemoteUser)
{
if (followee.Host != null)
{
var activity = activityRenderer.RenderUnfollow(user, followee);
await deliverSvc.DeliverToAsync(activity, user, followee);
}
var relationshipId = await db.Followings.Where(p => p.Follower == user && p.Followee == followee)
.Select(p => p.RelationshipId)
.FirstOrDefaultAsync() ??
await db.FollowRequests.Where(p => p.Follower == user && p.Followee == followee)
.Select(p => p.RelationshipId)
.FirstOrDefaultAsync();
var activity = activityRenderer.RenderUnfollow(user, followee, relationshipId);
await deliverSvc.DeliverToAsync(activity, user, followee);
}
if (followee.PrecomputedIsFollowedBy ?? false)
@ -787,7 +797,7 @@ public class UserService(
db.RemoveRange(followings);
await db.SaveChangesAsync();
if (followee.Host != null)
if (followee.IsRemoteUser)
{
_ = followupTaskSvc.ExecuteTask("DecrementInstanceOutgoingFollowsCounter", async provider =>
{