[backend/core] Allow configuring arbitrary reject/rewrite policies, add default configuration values to all policies (ISH-16)
This commit is contained in:
parent
a5a2c0b169
commit
dc77c48005
6 changed files with 79 additions and 82 deletions
|
@ -12,7 +12,6 @@ using Iceshrimp.Backend.Core.Federation.ActivityStreams;
|
||||||
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
using Iceshrimp.Backend.Core.Federation.ActivityStreams.Types;
|
||||||
using Iceshrimp.Backend.Core.Helpers;
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
using Iceshrimp.Backend.Core.Policies;
|
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
using Iceshrimp.Backend.Core.Tasks;
|
using Iceshrimp.Backend.Core.Tasks;
|
||||||
using Iceshrimp.Shared.Schemas.Web;
|
using Iceshrimp.Shared.Schemas.Web;
|
||||||
|
@ -20,6 +19,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
using static Iceshrimp.Backend.Core.Extensions.SwaggerGenOptionsExtensions;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Controllers.Web;
|
namespace Iceshrimp.Backend.Controllers.Web;
|
||||||
|
|
||||||
|
@ -187,87 +187,34 @@ public class AdminController(
|
||||||
await new MediaCleanupTask().Invoke(scope.ServiceProvider);
|
await new MediaCleanupTask().Invoke(scope.ServiceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("policy/word-reject")]
|
[HttpGet("policy")]
|
||||||
[ProducesResults(HttpStatusCode.OK)]
|
[ProducesResults(HttpStatusCode.OK)]
|
||||||
public async Task<WordRejectPolicyConfiguration> GetWordRejectPolicy()
|
public async Task<List<string>> GetAvailablePolicies() => await policySvc.GetAvailablePolicies();
|
||||||
{
|
|
||||||
var raw = await db.PolicyConfiguration.Where(p => p.Name == nameof(WordRejectPolicy))
|
|
||||||
.Select(p => p.Data)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
var res = raw != null ? JsonSerializer.Deserialize<WordRejectPolicyConfiguration>(raw) : null;
|
[HttpGet("policy/{name}")]
|
||||||
res ??= new WordRejectPolicyConfiguration { Enabled = false, Words = [] };
|
[ProducesResults(HttpStatusCode.OK)]
|
||||||
return res;
|
[ProducesErrors(HttpStatusCode.NotFound)]
|
||||||
|
public async Task<IPolicyConfiguration> GetPolicyConfiguration(string name)
|
||||||
|
{
|
||||||
|
var raw = await db.PolicyConfiguration.Where(p => p.Name == name).Select(p => p.Data).FirstOrDefaultAsync();
|
||||||
|
return await policySvc.GetConfiguration(name, raw) ?? throw GracefulException.NotFound("Policy not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("policy/user-age-reject")]
|
[HttpPut("policy/{name}")]
|
||||||
[ProducesResults(HttpStatusCode.OK)]
|
[ProducesResults(HttpStatusCode.OK)]
|
||||||
public async Task<UserAgeRejectPolicyConfiguration> GetUserAgeRejectPolicy()
|
[ProducesErrors(HttpStatusCode.BadRequest, HttpStatusCode.NotFound)]
|
||||||
|
public async Task UpdateWordRejectPolicy(
|
||||||
|
string name, [SwaggerBodyExample("{\n \"enabled\": true\n}")] JsonDocument body
|
||||||
|
)
|
||||||
{
|
{
|
||||||
var raw = await db.PolicyConfiguration.Where(p => p.Name == nameof(UserAgeRejectPolicy))
|
// @formatter:off
|
||||||
.Select(p => p.Data)
|
var type = await policySvc.GetConfigurationType(name) ?? throw GracefulException.NotFound("Policy not found");
|
||||||
.FirstOrDefaultAsync();
|
var data = body.Deserialize(type) as IPolicyConfiguration ?? throw GracefulException.BadRequest("Invalid policy config");
|
||||||
|
if (data.GetType() != type) throw GracefulException.BadRequest("Invalid policy config");
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
var res = raw != null ? JsonSerializer.Deserialize<UserAgeRejectPolicyConfiguration>(raw) : null;
|
|
||||||
res ??= new UserAgeRejectPolicyConfiguration { Enabled = false, Age = TimeSpan.Zero };
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet("policy/hellthread-reject")]
|
|
||||||
[ProducesResults(HttpStatusCode.OK)]
|
|
||||||
public async Task<HellthreadRejectPolicyConfiguration> GetHellthreadRejectPolicy()
|
|
||||||
{
|
|
||||||
var raw = await db.PolicyConfiguration.Where(p => p.Name == nameof(HellthreadRejectPolicy))
|
|
||||||
.Select(p => p.Data)
|
|
||||||
.FirstOrDefaultAsync();
|
|
||||||
|
|
||||||
var res = raw != null ? JsonSerializer.Deserialize<HellthreadRejectPolicyConfiguration>(raw) : null;
|
|
||||||
res ??= new HellthreadRejectPolicyConfiguration { Enabled = false, MentionLimit = 0 };
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("policy/word-reject")]
|
|
||||||
[ProducesResults(HttpStatusCode.OK)]
|
|
||||||
[ProducesErrors(HttpStatusCode.BadRequest)]
|
|
||||||
public async Task UpdateWordRejectPolicy(WordRejectPolicyConfiguration data)
|
|
||||||
{
|
|
||||||
await db.PolicyConfiguration
|
await db.PolicyConfiguration
|
||||||
.Upsert(new PolicyConfiguration
|
.Upsert(new PolicyConfiguration { Name = name, Data = JsonSerializer.Serialize(data) })
|
||||||
{
|
|
||||||
Name = nameof(WordRejectPolicy), Data = JsonSerializer.Serialize(data)
|
|
||||||
})
|
|
||||||
.On(p => new { p.Name })
|
|
||||||
.RunAsync();
|
|
||||||
|
|
||||||
await policySvc.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("policy/user-age-reject")]
|
|
||||||
[ProducesResults(HttpStatusCode.OK)]
|
|
||||||
[ProducesErrors(HttpStatusCode.BadRequest)]
|
|
||||||
public async Task UpdateUserAgeRejectPolicy(UserAgeRejectPolicyConfiguration data)
|
|
||||||
{
|
|
||||||
await db.PolicyConfiguration
|
|
||||||
.Upsert(new PolicyConfiguration
|
|
||||||
{
|
|
||||||
Name = nameof(UserAgeRejectPolicy), Data = JsonSerializer.Serialize(data)
|
|
||||||
})
|
|
||||||
.On(p => new { p.Name })
|
|
||||||
.RunAsync();
|
|
||||||
|
|
||||||
await policySvc.Update();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("policy/hellthread-reject")]
|
|
||||||
[ProducesResults(HttpStatusCode.OK)]
|
|
||||||
[ProducesErrors(HttpStatusCode.BadRequest)]
|
|
||||||
public async Task UpdateHellthreadRejectPolicy(HellthreadRejectPolicyConfiguration data)
|
|
||||||
{
|
|
||||||
await db.PolicyConfiguration.Upsert(new PolicyConfiguration
|
|
||||||
{
|
|
||||||
Name = nameof(HellthreadRejectPolicy),
|
|
||||||
Data = JsonSerializer.Serialize(data)
|
|
||||||
})
|
|
||||||
.On(p => new { p.Name })
|
.On(p => new { p.Name })
|
||||||
.RunAsync();
|
.RunAsync();
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ public static class SwaggerGenOptionsExtensions
|
||||||
public static void AddFilters(this SwaggerGenOptions options)
|
public static void AddFilters(this SwaggerGenOptions options)
|
||||||
{
|
{
|
||||||
options.SchemaFilter<RequireNonNullablePropertiesSchemaFilter>();
|
options.SchemaFilter<RequireNonNullablePropertiesSchemaFilter>();
|
||||||
|
options.SchemaFilter<SwaggerBodyExampleSchemaFilter>();
|
||||||
options.SupportNonNullableReferenceTypes(); // Sets Nullable flags appropriately.
|
options.SupportNonNullableReferenceTypes(); // Sets Nullable flags appropriately.
|
||||||
options.UseAllOfToExtendReferenceSchemas(); // Allows $ref enums to be nullable
|
options.UseAllOfToExtendReferenceSchemas(); // Allows $ref enums to be nullable
|
||||||
options.UseAllOfForInheritance(); // Allows $ref objects to be nullable
|
options.UseAllOfForInheritance(); // Allows $ref objects to be nullable
|
||||||
|
@ -68,6 +69,18 @@ public static class SwaggerGenOptionsExtensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local",
|
||||||
|
Justification = "SwaggerGenOptions.SchemaFilter<T> instantiates this class at runtime")]
|
||||||
|
private class SwaggerBodyExampleSchemaFilter : ISchemaFilter
|
||||||
|
{
|
||||||
|
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
|
||||||
|
{
|
||||||
|
var att = context.ParameterInfo?.GetCustomAttribute<SwaggerBodyExampleAttribute>();
|
||||||
|
if (att != null)
|
||||||
|
schema.Example = new OpenApiString(att.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local",
|
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Local",
|
||||||
Justification = "SwaggerGenOptions.OperationFilter<T> instantiates this class at runtime")]
|
Justification = "SwaggerGenOptions.OperationFilter<T> instantiates this class at runtime")]
|
||||||
private class AuthorizeCheckOperationDocumentFilter : IOperationFilter, IDocumentFilter
|
private class AuthorizeCheckOperationDocumentFilter : IOperationFilter, IDocumentFilter
|
||||||
|
@ -388,4 +401,9 @@ public static class SwaggerGenOptionsExtensions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SwaggerBodyExampleAttribute(string value) : Attribute
|
||||||
|
{
|
||||||
|
public string Value => value;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,6 @@ public class HellthreadRejectPolicyConfiguration : IPolicyConfiguration<Hellthre
|
||||||
public HellthreadRejectPolicy Apply() => new(Enabled, MentionLimit);
|
public HellthreadRejectPolicy Apply() => new(Enabled, MentionLimit);
|
||||||
IPolicy IPolicyConfiguration. Apply() => Apply();
|
IPolicy IPolicyConfiguration. Apply() => Apply();
|
||||||
|
|
||||||
public required bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
|
public int MentionLimit { get; set; }
|
||||||
public required int MentionLimit { get; set; }
|
|
||||||
}
|
}
|
|
@ -15,6 +15,6 @@ public class UserAgeRejectPolicyConfiguration : IPolicyConfiguration<UserAgeReje
|
||||||
public UserAgeRejectPolicy Apply() => new(Enabled, Age);
|
public UserAgeRejectPolicy Apply() => new(Enabled, Age);
|
||||||
IPolicy IPolicyConfiguration.Apply() => Apply();
|
IPolicy IPolicyConfiguration.Apply() => Apply();
|
||||||
|
|
||||||
public required bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public required TimeSpan Age { get; set; }
|
public TimeSpan Age { get; set; } = TimeSpan.Zero;
|
||||||
}
|
}
|
|
@ -45,6 +45,6 @@ public class WordRejectPolicyConfiguration : IPolicyConfiguration<WordRejectPoli
|
||||||
public WordRejectPolicy Apply() => new(Enabled, Words);
|
public WordRejectPolicy Apply() => new(Enabled, Words);
|
||||||
IPolicy IPolicyConfiguration.Apply() => Apply();
|
IPolicy IPolicyConfiguration.Apply() => Apply();
|
||||||
|
|
||||||
public required bool Enabled { get; set; }
|
public bool Enabled { get; set; }
|
||||||
public required string[] Words { get; set; }
|
public string[] Words { get; set; } = [];
|
||||||
}
|
}
|
|
@ -81,6 +81,39 @@ public class PolicyService(IServiceScopeFactory scopeFactory)
|
||||||
|
|
||||||
foreach (var hook in hooks) hook.Apply(data);
|
foreach (var hook in hooks) hook.Apply(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<Type?> GetConfigurationType(string name)
|
||||||
|
{
|
||||||
|
await Initialize();
|
||||||
|
var type = _policyTypes.FirstOrDefault(p => p.Name == name);
|
||||||
|
return _policyConfigurationTypes
|
||||||
|
.FirstOrDefault(p => p.GetInterfaces()
|
||||||
|
.FirstOrDefault(i => i.Name == typeof(IPolicyConfiguration<>).Name)
|
||||||
|
?.GenericTypeArguments.FirstOrDefault() ==
|
||||||
|
type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IPolicyConfiguration?> GetConfiguration(string name, string? data)
|
||||||
|
{
|
||||||
|
var type = await GetConfigurationType(name);
|
||||||
|
if (type == null) return null;
|
||||||
|
|
||||||
|
var cType = _policyConfigurationTypes
|
||||||
|
.FirstOrDefault(p => p.GetInterfaces()
|
||||||
|
.FirstOrDefault(i => i.Name == typeof(IPolicyConfiguration<>).Name)
|
||||||
|
?.GenericTypeArguments.FirstOrDefault() ==
|
||||||
|
type);
|
||||||
|
|
||||||
|
if (cType == null) return null;
|
||||||
|
if (data == null) return (IPolicyConfiguration?)Activator.CreateInstance(cType);
|
||||||
|
return (IPolicyConfiguration?)JsonSerializer.Deserialize(data, cType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<string>> GetAvailablePolicies()
|
||||||
|
{
|
||||||
|
await Initialize();
|
||||||
|
return _policyTypes.Select(p => p.Name).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPolicy
|
public interface IPolicy
|
||||||
|
@ -115,7 +148,7 @@ public interface IPolicyConfiguration
|
||||||
public IPolicy Apply();
|
public IPolicy Apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IPolicyConfiguration<out TPolicy> : IPolicyConfiguration
|
public interface IPolicyConfiguration<out TPolicy> : IPolicyConfiguration where TPolicy : IPolicy
|
||||||
{
|
{
|
||||||
public new TPolicy Apply();
|
public new TPolicy Apply();
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue