[backend/federation] Improve actor & note validation (ISH-547)

This commit is contained in:
Laura Hausmann 2024-10-23 20:40:31 +02:00
parent c0e8a6d680
commit 863c9ca9c9
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
4 changed files with 26 additions and 8 deletions

View file

@ -74,7 +74,7 @@ public class UserResolver(
}
logger.LogDebug("Actor ID matches query, performing reverse discovery...");
actor.Normalize(query);
actor.NormalizeAndValidate(query);
var domain = new Uri(actor.Id).Host;
var username = actor.Username!;
return await WebFingerAsync(actor.WebfingerAddress ?? $"acct:{username}@{domain}", false, actor.Id);
@ -124,7 +124,7 @@ public class UserResolver(
throw new Exception("Reverse discovery fallback failed: uri mismatch");
logger.LogDebug("Actor ID matches apUri, performing reverse discovery...");
actor.Normalize(apUri);
actor.NormalizeAndValidate(apUri);
var domain = new Uri(actor.Id).Host;
var username = new Uri(actor.Username!).Host;
return await WebFingerAsync(actor.WebfingerAddress ?? $"acct:{username}@{domain}", false,

View file

@ -1,6 +1,7 @@
using System.Text.RegularExpressions;
using Iceshrimp.Backend.Core.Configuration;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Middleware;
using J = Newtonsoft.Json.JsonPropertyAttribute;
using JC = Newtonsoft.Json.JsonConverterAttribute;
using JI = Newtonsoft.Json.JsonIgnoreAttribute;
@ -141,7 +142,7 @@ public class ASActor : ASObjectWithId
[JI] public bool IsBot => Type == $"{Constants.ActivityStreamsNs}#Service";
public void Normalize(string uri)
public void NormalizeAndValidate(string uri)
{
if (Type == null || !ActorTypes.Contains(Type)) throw new Exception("Actor is of invalid type");
@ -154,9 +155,26 @@ public class ASActor : ASObjectWithId
!Regex.IsMatch(Username, @"^\w([\w-.]*\w)?$"))
throw new Exception("Actor username is invalid");
var uriHost = new Uri(uri).Host;
var publicKeyId = PublicKey?.Id ?? throw new Exception("Invalid actor: missing PublicKey?.Id");
var sharedInbox = SharedInbox?.Link ?? Endpoints?.SharedInbox?.Id;
if (Inbox?.Id == null)
throw GracefulException.UnprocessableEntity("Invalid actor: missing inbox");
if (new Uri(publicKeyId).Host != new Uri(uri).Host)
throw new Exception("Invalid actor: public key id / actor id host mismatch");
throw GracefulException.UnprocessableEntity("Invalid actor: public key id / actor id host mismatch");
if (new Uri(Inbox.Id).Host != uriHost)
throw GracefulException.UnprocessableEntity("Invalid actor: inbox host doesn't match id host");
if (Outbox?.Id != null && new Uri(Outbox.Id).Host != uriHost)
throw GracefulException.UnprocessableEntity("Invalid actor: outbox doesn't match id host");
if (sharedInbox != null && new Uri(sharedInbox).Host != uriHost)
throw GracefulException.UnprocessableEntity("Invalid actor: shared inbox host doesn't match id host");
if (Followers?.Id != null && new Uri(Followers.Id).Host != uriHost)
throw GracefulException.UnprocessableEntity("Invalid actor: followers host doesn't match actor id host");
if (Following?.Id != null && new Uri(Following.Id).Host != uriHost)
throw GracefulException.UnprocessableEntity("Invalid actor: following host doesn't match id host");
if (Url?.Link != null && new Uri(Url.Link).Host != uriHost)
Url = null;
DisplayName = DisplayName switch
{

View file

@ -817,6 +817,8 @@ public class NoteService(
throw GracefulException.UnprocessableEntity("Note.Id schema is invalid");
if (note.Url?.Link != null && !note.Url.Link.StartsWith("https://"))
throw GracefulException.UnprocessableEntity("Note.Url schema is invalid");
if (note.Url?.Link != null && new Uri(note.Id).IdnHost != new Uri(note.Url.Link).IdnHost)
note.Url = null;
if (actor.IsSuspended)
throw GracefulException.Forbidden("User is suspended");
if (await fedCtrlSvc.ShouldBlockAsync(note.Id, actor.Host))

View file

@ -130,7 +130,7 @@ public class UserService(
var actor = await fetchSvc.FetchActorAsync(uri);
logger.LogDebug("Got actor: {url}", actor.Url);
actor.Normalize(uri);
actor.NormalizeAndValidate(uri);
user = await db.Users.FirstOrDefaultAsync(p => p.UsernameLower == actor.Username!.ToLowerInvariant() &&
p.Host == host);
@ -142,8 +142,6 @@ public class UserService(
throw GracefulException.UnprocessableEntity("Uri doesn't match id of fetched actor");
if (actor.PublicKey?.Id == null || actor.PublicKey?.PublicKey == null)
throw GracefulException.UnprocessableEntity("Actor has no valid public key");
if (new Uri(actor.PublicKey.Id).Host != new Uri(actor.Id).Host)
throw GracefulException.UnprocessableEntity("Actor public key id host doesn't match actor id host");
var emoji = await emojiSvc.ProcessEmojiAsync(actor.Tags?.OfType<ASEmoji>().ToList(), host);
@ -278,7 +276,7 @@ public class UserService(
logger.LogDebug("Updating user with uri {uri}", uri);
actor ??= await fetchSvc.FetchActorAsync(user.Uri);
actor.Normalize(uri);
actor.NormalizeAndValidate(uri);
user.UserProfile ??= await db.UserProfiles.FirstOrDefaultAsync(p => p.User == user);
user.UserProfile ??= new UserProfile { User = user };