Domain blocks: suspend, silence, reject media
DomainBlock (domain, severity, reject-media, public and private comment)
covers the domain and its subdomains. Admins manage them under
/clientapi/admin/domainblocks/{list,insert,delete}; the set is kept in
memory, reloaded on every change and at most five minutes stale.
A suspended domain is refused by FederationHttp.IsAllowed, so nothing is
fetched from it and no job delivers to it, and the inbox drops its
activities with a 202 before fetching any key. Reject-media strips the
attachments of posts from that domain. Silence is recorded for the
timelines and notifications that arrive in P1.2.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
This commit is contained in:
1 parent
9ec1f9930b
commit
e6a362c0b8
13 files changed
+318
-13
No files matched your search
@@ -6,7 +6,9 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using PrivaPub.Federation.Moderation;
|
||||
using PrivaPub.Infrastructure.Http;
|
||||
using PrivaPub.Models.Federation;
|
||||
|
||||
namespace PrivaPub.Tests.Infrastructure
|
||||
{
|
||||
@@ -39,7 +41,7 @@ namespace PrivaPub.Tests.Infrastructure
|
||||
.ConfigurePrimaryHttpMessageHandler(() => SafeHttpHandlerFactory.Create(options));
|
||||
var provider = services.BuildServiceProvider();
|
||||
return new FederationHttp(provider.GetRequiredService<IHttpClientFactory>(), new MemoryCache(new MemoryCacheOptions()),
|
||||
new StaticOptionsMonitor(options), NullLogger<FederationHttp>.Instance);
|
||||
new StaticOptionsMonitor(options), new StaticBlocks(), NullLogger<FederationHttp>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -91,6 +93,29 @@ namespace PrivaPub.Tests.Infrastructure
|
||||
public void IsAllowed_takes_https_dns_names_only(string url, bool allowed) =>
|
||||
Assert.Equal(allowed, Client(allowTestNetwork: false).IsAllowed(new Uri(url)));
|
||||
|
||||
[Fact]
|
||||
public void IsAllowed_refuses_a_suspended_domain_and_its_subdomains()
|
||||
{
|
||||
var http = new FederationHttp(new ServiceCollection().AddHttpClient().BuildServiceProvider().GetRequiredService<IHttpClientFactory>(),
|
||||
new MemoryCache(new MemoryCacheOptions()), new StaticOptionsMonitor(new FederationOptions()), new StaticBlocks("evil.example"),
|
||||
NullLogger<FederationHttp>.Instance);
|
||||
|
||||
Assert.False(http.IsAllowed(new Uri("https://evil.example/users/x")));
|
||||
Assert.False(http.IsAllowed(new Uri("https://cdn.evil.example/a.png")));
|
||||
Assert.True(http.IsAllowed(new Uri("https://notevil.example/users/x")));
|
||||
}
|
||||
|
||||
sealed class StaticBlocks : IDomainBlocks
|
||||
{
|
||||
readonly string[] _suspended;
|
||||
public StaticBlocks(params string[] suspended) => _suspended = suspended;
|
||||
public DomainBlock Find(string host) => _suspended.Any(s => host == s || host.EndsWith("." + s))
|
||||
? new DomainBlock { Domain = host, Severity = DomainBlockSeverity.Suspend }
|
||||
: default;
|
||||
public bool IsSuspended(string host) => Find(host) != default;
|
||||
public Task Reload(CancellationToken token) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
sealed class StaticOptionsMonitor : IOptionsMonitor<FederationOptions>
|
||||
{
|
||||
public StaticOptionsMonitor(FederationOptions value) => CurrentValue = value;
|
||||
|
||||
Reference in new issue
Block a user