[backend/core] Add basic message rewrite policies (ISH-16)

This commit is contained in:
Laura Hausmann 2024-10-09 21:14:50 +02:00
parent 5f5a0c5c0f
commit 5390990448
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
5 changed files with 231 additions and 0 deletions

View file

@ -0,0 +1,103 @@
using System.Text.RegularExpressions;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Extensions;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Backend.Core.Services;
namespace Iceshrimp.Backend.Core.Policies;
public class ForceCwRewritePolicy(
bool enabled,
int priority,
string cw,
string[] words,
string[] instances
) : IRewritePolicy
{
public string Name => nameof(ForceCwRewritePolicy);
public bool Enabled => enabled;
public int Priority => priority;
public IRewritePolicy.HookLocationEnum HookLocation => IRewritePolicy.HookLocationEnum.PreLogic;
public void Apply(NoteService.NoteCreationData data)
{
if (IsApplicable(data))
data.Cw = Apply(data.Cw);
}
public void Apply(NoteService.NoteUpdateData data)
{
if (IsApplicable(data))
data.Cw = Apply(data.Cw);
}
public void Apply(ASNote note, User actor) { }
private string Apply(string? oldCw)
{
if (oldCw == null)
return cw;
if (oldCw.ToLowerInvariant().Contains(cw.ToLowerInvariant()))
return oldCw;
return oldCw + $", {cw}";
}
private bool IsApplicable(NoteService.NoteCreationData data)
{
if (instances.Contains(data.User.Host)) return true;
return IsApplicable([
data.Text, data.Cw, ..data.Poll?.Choices ?? [], ..data.Attachments?.Select(p => p.Comment) ?? []
]);
}
private bool IsApplicable(NoteService.NoteUpdateData data)
{
if (instances.Contains(data.Note.User.Host)) return true;
return IsApplicable([
data.Text, data.Cw, ..data.Poll?.Choices ?? [], ..data.Attachments?.Select(p => p.Comment) ?? []
]);
}
private bool IsApplicable(IEnumerable<string?> candidates)
{
foreach (var candidate in candidates.NotNull())
{
foreach (var keyword in words)
{
if (keyword.StartsWith('"') && keyword.EndsWith('"'))
{
var pattern = $@"\b{EfHelpers.EscapeRegexQuery(keyword[1..^1])}\b";
var regex = new Regex(pattern, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(10));
if (regex.IsMatch(candidate))
return true;
}
else if (candidate.Contains(keyword, StringComparison.InvariantCultureIgnoreCase))
{
return true;
}
}
}
return false;
}
}
public class ForceCwRewritePolicyConfiguration : IPolicyConfiguration<ForceCwRewritePolicy>
{
private string[] _instances = [];
public ForceCwRewritePolicy Apply() => new(Enabled, Priority, Cw, Words, Instances);
IPolicy IPolicyConfiguration.Apply() => Apply();
public bool Enabled { get; set; }
public int Priority { get; set; }
public string Cw { get; set; } = "forced content warning";
public string[] Words { get; set; } = [];
public string[] Instances
{
get => _instances;
set => _instances = value.Select(p => p.ToLowerInvariant()).ToArray();
}
}

View file

@ -0,0 +1,56 @@
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Services;
namespace Iceshrimp.Backend.Core.Policies;
public class ForceMediaSensitiveRewritePolicy(
bool enabled,
int priority,
string[] instances
) : IRewritePolicy
{
public string Name => nameof(ForceMediaSensitiveRewritePolicy);
public bool Enabled => enabled;
public int Priority => priority;
public IRewritePolicy.HookLocationEnum HookLocation => IRewritePolicy.HookLocationEnum.PreLogic;
public void Apply(NoteService.NoteCreationData data)
{
if (IsApplicable(data))
Apply(data.Attachments);
}
public void Apply(NoteService.NoteUpdateData data)
{
if (IsApplicable(data))
Apply(data.Attachments);
}
public void Apply(ASNote note, User actor) { }
private void Apply(IEnumerable<DriveFile>? files)
{
if (files == null) return;
foreach (var file in files) file.IsSensitive = true;
}
private bool IsApplicable(NoteService.NoteCreationData data) => instances.Contains(data.User.Host);
private bool IsApplicable(NoteService.NoteUpdateData data) => instances.Contains(data.Note.User.Host);
}
public class ForceMediaSensitivePolicyConfiguration : IPolicyConfiguration<ForceMediaSensitiveRewritePolicy>
{
private string[] _instances = [];
public ForceMediaSensitiveRewritePolicy Apply() => new(Enabled, Priority, Instances);
IPolicy IPolicyConfiguration. Apply() => Apply();
public bool Enabled { get; set; }
public int Priority { get; set; }
public string[] Instances
{
get => _instances;
set => _instances = value.Select(p => p.ToLowerInvariant()).ToArray();
}
}

View file

@ -0,0 +1,56 @@
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Services;
namespace Iceshrimp.Backend.Core.Policies;
public class StripMediaRewritePolicy(
bool enabled,
int priority,
string[] instances
) : IRewritePolicy
{
public string Name => nameof(StripMediaRewritePolicy);
public bool Enabled => enabled;
public int Priority => priority;
public IRewritePolicy.HookLocationEnum HookLocation => IRewritePolicy.HookLocationEnum.PreLogic;
public void Apply(NoteService.NoteCreationData data)
{
if (data.User.Host == null) return;
if (IsApplicable(data.User.Host))
data.Attachments = [];
}
public void Apply(NoteService.NoteUpdateData data)
{
if (data.Note.User.Host == null) return;
if (IsApplicable(data.Note.User.Host))
data.Attachments = [];
}
public void Apply(ASNote note, User actor)
{
if (actor.Host == null) return;
if (IsApplicable(actor.Host))
note.Attachments = [];
}
private bool IsApplicable(string host) => instances.Contains(host);
}
public class StripMediaRewritePolicyConfiguration : IPolicyConfiguration<StripMediaRewritePolicy>
{
private string[] _instances = [];
public StripMediaRewritePolicy Apply() => new(Enabled, Priority, Instances);
IPolicy IPolicyConfiguration. Apply() => Apply();
public bool Enabled { get; set; }
public int Priority { get; set; }
public string[] Instances
{
get => _instances;
set => _instances = value.Select(p => p.ToLowerInvariant()).ToArray();
}
}

View file

@ -849,6 +849,8 @@ public class NoteService(
if (await fedCtrlSvc.ShouldBlockAsync(note.Id, actor.Host))
throw GracefulException.UnprocessableEntity("Refusing to create note for user on blocked instance");
policySvc.CallRewriteHooks(note, actor, IRewritePolicy.HookLocationEnum.PreLogic);
var replyUri = note.InReplyTo?.Id;
var reply = replyUri != null ? await ResolveNoteAsync(replyUri, user: user) : null;
Poll? poll = null;
@ -946,6 +948,8 @@ public class NoteService(
.Select(p => p.Id)
.ToList();
policySvc.CallRewriteHooks(note, actor, IRewritePolicy.HookLocationEnum.PostLogic);
return await CreateNoteAsync(new NoteCreationData
{
User = actor,

View file

@ -3,6 +3,8 @@ using System.Reflection;
using System.Text.Json;
using Iceshrimp.AssemblyUtils;
using Iceshrimp.Backend.Core.Database;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
using Iceshrimp.Backend.Core.Helpers;
using Iceshrimp.Shared.Configuration;
using Microsoft.EntityFrameworkCore;
@ -84,6 +86,14 @@ public class PolicyService(IServiceScopeFactory scopeFactory)
foreach (var hook in hooks) hook.Apply(data);
}
public void CallRewriteHooks(ASNote note, User actor, IRewritePolicy.HookLocationEnum location)
{
var hooks = _rewritePolicies.Where(p => p.Enabled && p.HookLocation == location)
.OrderByDescending(p => p.Priority);
foreach (var hook in hooks) hook.Apply(note, actor);
}
public async Task<Type?> GetConfigurationType(string name)
{
await Initialize();
@ -129,11 +139,13 @@ public interface IRewritePolicy : IPolicy
public void Apply(NoteService.NoteCreationData data);
public void Apply(NoteService.NoteUpdateData data);
public void Apply(ASNote note, User actor);
}
public interface IRejectPolicy : IPolicy
{
public bool ShouldReject(NoteService.NoteCreationData data);
//TODO: reject during note update & note process
}
public interface IPolicyConfiguration