[backend/federation] Improve actor & note validation (ISH-547)
This commit is contained in:
parent
c0e8a6d680
commit
863c9ca9c9
4 changed files with 26 additions and 8 deletions
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 };
|
||||
|
|
Loading…
Add table
Reference in a new issue