[backend/federation] Fix user profile mention resolution recursion limiter (ISH-33)

This commit is contained in:
Laura Hausmann 2024-02-25 02:11:36 +01:00
parent c4a0cf528d
commit 3a2e1bd3b2
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
5 changed files with 33 additions and 14 deletions

View file

@ -25,7 +25,6 @@ public static class ServiceExtensions
public static void AddServices(this IServiceCollection services)
{
// Transient = instantiated per request and class
services.AddTransient<FollowupTaskService>();
// Scoped = instantiated per request
services
@ -56,7 +55,8 @@ public static class ServiceExtensions
.AddScoped<NoteRenderer>()
.AddScoped<UserRenderer>()
.AddScoped<NotificationRenderer>()
.AddScoped<ActivityPubController>();
.AddScoped<ActivityPubController>()
.AddScoped<FollowupTaskService>();
// Singleton = instantiated once across application lifetime
services

View file

@ -133,15 +133,17 @@ public class UserResolver(
}
}
public async Task<User?> ResolveAsyncLimited(string query, Func<bool> limitReached)
public async Task<User?> ResolveAsyncLimited(string username, string? host, Func<bool> limitReached)
{
query = NormalizeQuery(query);
var query = $"acct:{username}@{host}";
// First, let's see if we already know the user
var user = await userSvc.GetUserFromQueryAsync(query);
if (user != null)
return await GetUpdatedUser(user);
if (host == null) return null;
// We don't, so we need to run WebFinger
var (acct, uri) = await WebFingerAsync(query);

View file

@ -2,6 +2,10 @@ namespace Iceshrimp.Backend.Core.Services;
public class FollowupTaskService(IServiceScopeFactory serviceScopeFactory)
{
public bool IsBackgroundWorker { get; private set; }
public IServiceProvider? ServiceProvider { get; set; }
public Task ExecuteTask(string taskName, Func<IServiceProvider, Task> work)
{
return Task.Run(async () =>
@ -10,6 +14,9 @@ public class FollowupTaskService(IServiceScopeFactory serviceScopeFactory)
try
{
var provider = scope.ServiceProvider;
var instance = provider.GetRequiredService<FollowupTaskService>();
instance.IsBackgroundWorker = true;
instance.ServiceProvider = ServiceProvider ?? provider;
await work(provider);
}
catch (Exception e)

View file

@ -8,11 +8,11 @@ using Microsoft.Extensions.Options;
namespace Iceshrimp.Backend.Core.Services;
public class UserProfileMentionsResolver(ActivityPub.UserResolver userResolver, IOptions<Config.InstanceSection> config)
public class UserProfileMentionsResolver(ActivityPub.UserResolver userResolver, IOptions<Config.InstanceSection> config, ILogger<UserProfileMentionsResolver> logger)
{
private int _recursionLimit = 10;
public async Task<List<Note.MentionedUser>> ResolveMentions(UserProfile.Field[]? fields, string? bio)
public async Task<List<Note.MentionedUser>> ResolveMentions(UserProfile.Field[]? fields, string? bio, string? host)
{
if (fields is not { Length: > 0 } && bio == null) return [];
var input = (fields ?? [])
@ -29,8 +29,12 @@ public class UserProfileMentionsResolver(ActivityPub.UserResolver userResolver,
{
try
{
return await userResolver.ResolveAsyncLimited(p.Acct,
() => --_recursionLimit >= 0);
return await userResolver.ResolveAsyncLimited(p.Username, p.Host ?? host,
() =>
{
logger.LogDebug("Recursion limiter is at: {limit}", _recursionLimit);
return _recursionLimit-- <= 0;
});
}
catch
{
@ -41,6 +45,7 @@ public class UserProfileMentionsResolver(ActivityPub.UserResolver userResolver,
return users.Where(p => p != null)
.Cast<User>()
.DistinctBy(p => p.Id)
.Select(p => new Note.MentionedUser
{
Host = p.Host,

View file

@ -165,7 +165,7 @@ public class UserService(
var processPendingDeletes = await ResolveAvatarAndBanner(user, actor);
await db.SaveChangesAsync();
await processPendingDeletes();
UpdateProfileMentionsInBackground(user);
await UpdateProfileMentionsInBackground(user);
return user;
}
catch (UniqueConstraintException)
@ -270,7 +270,7 @@ public class UserService(
db.Update(user);
await db.SaveChangesAsync();
await processPendingDeletes();
UpdateProfileMentionsInBackground(user);
await UpdateProfileMentionsInBackground(user);
return user;
}
@ -605,18 +605,23 @@ public class UserService(
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataQuery", Justification = "Projectables")]
[SuppressMessage("ReSharper", "EntityFramework.NPlusOne.IncompleteDataUsage", Justification = "Same as above")]
[SuppressMessage("ReSharper", "SuggestBaseTypeForParameter", Justification = "Method only makes sense for users")]
private void UpdateProfileMentionsInBackground(User user)
private async Task UpdateProfileMentionsInBackground(User user)
{
_ = followupTaskSvc.ExecuteTask("UpdateProfileMentionsInBackground", async provider =>
var task = followupTaskSvc.ExecuteTask("UpdateProfileMentionsInBackground", async provider =>
{
var bgDbContext = provider.GetRequiredService<DatabaseContext>();
var bgMentionsResolver = provider.GetRequiredService<UserProfileMentionsResolver>();
var bgMentionsResolver = (followupTaskSvc.ServiceProvider ?? provider)
.GetRequiredService<UserProfileMentionsResolver>();
var bgUser = await bgDbContext.Users.IncludeCommonProperties().FirstOrDefaultAsync(p => p.Id == user.Id);
if (bgUser?.UserProfile == null) return;
bgUser.UserProfile.Mentions =
await bgMentionsResolver.ResolveMentions(bgUser.UserProfile.Fields, bgUser.UserProfile.Description);
await bgMentionsResolver.ResolveMentions(bgUser.UserProfile.Fields, bgUser.UserProfile.Description,
bgUser.Host);
bgDbContext.Update(bgUser.UserProfile);
await bgDbContext.SaveChangesAsync();
});
if (followupTaskSvc.IsBackgroundWorker)
await task;
}
}