[backend/core] Federate blocks (ISH-169)

This commit is contained in:
Laura Hausmann 2024-03-14 15:47:44 +01:00
parent 085671b00c
commit 3a866d04dd
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
6 changed files with 88 additions and 12 deletions

View file

@ -469,7 +469,7 @@ public class User : IEntity
[InverseProperty(nameof(Tables.UserPublickey.User))]
public virtual UserPublickey? UserPublickey { get; set; }
[InverseProperty(nameof(Tables.UserSettings.User))]
public virtual UserSettings? UserSettings { get; set; }
@ -592,4 +592,7 @@ public class User : IEntity
: throw new Exception("Cannot access PublicUrl for remote user");
public string GetIdenticonUrl(string webDomain) => $"https://{webDomain}/identicon/{Id}";
public bool IsLocalUser => Host == null;
public bool IsRemoteUser => Host != null;
}

View file

@ -154,6 +154,9 @@ public class ActivityHandlerService(
case ASEmojiReact { Object: ASNote note } react:
await noteSvc.RemoveReactionFromNoteAsync(note, resolvedActor, react.Content);
return;
case ASBlock { Object: ASActor blockee }:
await UnblockAsync(resolvedActor, blockee);
return;
default:
throw GracefulException.UnprocessableEntity("Undo activity object is invalid");
}
@ -266,6 +269,18 @@ public class ActivityHandlerService(
await noteSvc.ReactToNoteAsync(note, resolvedActor, reaction.Content);
return;
}
case ASBlock block:
{
if (block.Object is not ASActor blockee)
throw GracefulException.UnprocessableEntity("Invalid or unsupported block target");
var resolvedBlockee = await userResolver.ResolveAsync(blockee.Id, true);
if (resolvedBlockee == null)
throw GracefulException.UnprocessableEntity("Unknown block target");
if (resolvedBlockee.Host != null)
throw GracefulException.UnprocessableEntity("Refusing to process block between two remote users");
await userSvc.BlockUserAsync(resolvedActor, resolvedBlockee);
return;
}
default:
throw new NotImplementedException($"Activity type {activity.Type} is unknown");
}
@ -409,6 +424,17 @@ public class ActivityHandlerService(
}
}
private async Task UnblockAsync(User blocker, ASActor blockee)
{
var resolvedBlockee = await userResolver.ResolveAsync(blockee.Id, true);
if (resolvedBlockee == null)
throw GracefulException.UnprocessableEntity("Unknown block target");
if (resolvedBlockee.Host != null)
throw GracefulException
.UnprocessableEntity("Refusing to process unblock between two remote users");
await userSvc.UnblockUserAsync(blocker, resolvedBlockee);
}
private async Task AcceptAsync(ASFollow obj, User actor)
{
var prefix = $"https://{config.Value.WebDomain}/follows/";

View file

@ -44,7 +44,9 @@ public class ActivityRenderer(
public ASDelete RenderDelete(ASActor actor, ASObject obj) => new()
{
Id = $"{obj.Id}#Delete", Actor = actor.Compact(), Object = obj
Id = $"{obj.Id}#Delete",
Actor = actor.Compact(),
Object = obj
};
public ASAccept RenderAccept(User followee, User follower, string requestId) => new()
@ -56,7 +58,9 @@ public class ActivityRenderer(
public ASAccept RenderAccept(ASActor actor, ASObject obj) => new()
{
Id = GenerateActivityId(), Actor = actor.Compact(), Object = obj
Id = GenerateActivityId(),
Actor = actor.Compact(),
Object = obj
};
public ASLike RenderLike(NoteLike like)
@ -93,7 +97,9 @@ public class ActivityRenderer(
var e = new ASEmoji
{
Id = emoji.PublicUrl, Name = name, Image = new ASImage { Url = new ASLink(emoji.PublicUrl) }
Id = emoji.PublicUrl,
Name = name,
Image = new ASImage { Url = new ASLink(emoji.PublicUrl) }
};
res.Tags = [e];
@ -137,17 +143,23 @@ public class ActivityRenderer(
public static ASFollow RenderFollow(ASObject followerActor, ASObject followeeActor, string requestId) => new()
{
Id = requestId, Actor = ASActor.FromObject(followerActor), Object = ASActor.FromObject(followeeActor)
Id = requestId,
Actor = ASActor.FromObject(followerActor),
Object = ASActor.FromObject(followeeActor)
};
public ASUndo RenderUndo(ASActor actor, ASObject obj) => new()
{
Id = GenerateActivityId(), Actor = actor.Compact(), Object = obj
Id = GenerateActivityId(),
Actor = actor.Compact(),
Object = obj
};
public ASReject RenderReject(ASActor actor, ASObject obj) => new()
{
Id = GenerateActivityId(), Actor = actor.Compact(), Object = obj
Id = GenerateActivityId(),
Actor = actor.Compact(),
Object = obj
};
public ASReject RenderReject(User followee, User follower, string requestId) => new()
@ -157,6 +169,13 @@ public class ActivityRenderer(
Object = RenderFollow(userRenderer.RenderLite(follower), userRenderer.RenderLite(followee), requestId)
};
public ASBlock RenderBlock(ASActor actor, ASActor obj, string blockId) => new()
{
Id = $"https://{config.Value.WebDomain}/blocks/{blockId}",
Actor = actor.Compact(),
Object = obj.Compact()
};
[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}";

View file

@ -33,6 +33,7 @@ public class ASActivity : ASObject
public const string Reject = $"{Ns}#Reject";
public const string Undo = $"{Ns}#Undo";
public const string Like = $"{Ns}#Like";
public const string Block = $"{Ns}#Block";
// Extensions
public const string Bite = "https://ns.mia.jetzt/as#Bite";
@ -116,14 +117,19 @@ public class ASUndo : ASActivity
public ASUndo() => Type = Types.Undo;
}
public class ASBlock : ASActivity
{
public ASBlock() => Type = Types.Block;
}
public class ASLike : ASActivity
{
public ASLike() => Type = Types.Like;
[J($"{Constants.MisskeyNs}#_misskey_reaction")]
[JC(typeof(VC))]
public string? MisskeyReaction { get; set; }
[J($"{Constants.ActivityStreamsNs}#tag")]
[JC(typeof(ASTagConverter))]
public List<ASTag>? Tags { get; set; }
@ -180,7 +186,7 @@ public class ASEmojiReact : ASActivity
[J($"{Constants.ActivityStreamsNs}#content")]
[JC(typeof(VC))]
public required string Content { get; set; }
[J($"{Constants.ActivityStreamsNs}#tag")]
[JC(typeof(ASTagConverter))]
public List<ASTag>? Tags { get; set; }

View file

@ -59,6 +59,7 @@ public class ASObject : ASObjectBase
ASActivity.Types.Bite => token.ToObject<ASBite>(),
ASActivity.Types.Announce => token.ToObject<ASAnnounce>(),
ASActivity.Types.EmojiReact => token.ToObject<ASEmojiReact>(),
ASActivity.Types.Block => token.ToObject<ASBlock>(),
_ => token.ToObject<ASObject>()
};
case JTokenType.Array:

View file

@ -914,6 +914,14 @@ public class UserService(
await db.AddAsync(blocking);
await db.SaveChangesAsync();
if (blocker.IsLocalUser && blockee.IsRemoteUser)
{
var actor = userRenderer.RenderLite(blocker);
var obj = userRenderer.RenderLite(blockee);
var activity = activityRenderer.RenderBlock(actor, obj, blocking.Id);
await deliverSvc.DeliverToAsync(activity, blocker, blockee);
}
}
public async Task UnblockUserAsync(User blocker, User blockee)
@ -921,8 +929,21 @@ public class UserService(
if (!blockee.PrecomputedIsBlockedBy ?? false)
return;
await db.Blockings.Where(p => p.Blocker == blocker && p.Blockee == blockee).ExecuteDeleteAsync();
blockee.PrecomputedIsBlockedBy = false;
var blocking = await db.Blockings.FirstOrDefaultAsync(p => p.Blocker == blocker && p.Blockee == blockee);
if (blocking == null) return;
db.Remove(blocking);
await db.SaveChangesAsync();
if (blocker.IsLocalUser && blockee.IsRemoteUser)
{
var actor = userRenderer.RenderLite(blocker);
var obj = userRenderer.RenderLite(blockee);
var block = activityRenderer.RenderBlock(actor, obj, blocking.Id);
var activity = activityRenderer.RenderUndo(actor, block);
await deliverSvc.DeliverToAsync(activity, blocker, blockee);
}
}
}