using Microsoft.Extensions.Caching.Memory; using MongoDB.Entities; using PrivaPub.Domain.Relationships; using PrivaPub.Federation.Actors; using PrivaPub.Infrastructure.Http; using PrivaPub.Models.Social; using PrivaPub.Tests.Support; using PrivaPub.Tests.Support.Host; using System.Text.Json.Nodes; namespace PrivaPub.Tests.Federation { // an account's handle: user@host, or the domain its `webfinger` names (FEP-2c59; Mastodon's LOCAL_DOMAIN apart from // its WEB_DOMAIN) once that domain's WebFinger says the handle is this account [Trait("Category", "Integration")] public sealed class RemoteHandleTests : IAsyncLifetime { Harness _harness; RemoteActorService _remote; public async ValueTask InitializeAsync() { Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip); _harness = await Harness.Start(); _remote = new RemoteActorService(Peer.Http(), _harness.Local, new MemoryCache(new MemoryCacheOptions()), _harness.Db, new StaticOptions(new FederationOptions { AllowPrivateNetworks = true, AllowPlainHttp = true })); } public async ValueTask DisposeAsync() { if (_harness != default) await _harness.DisposeAsync(); } // the actor at the peer's first address, its handle at the second RemoteActor Actor(string name) => Serve(new RemoteActor(_harness.Peer, name)); RemoteActor Serve(RemoteActor actor, string preferredUsername = default) { var document = actor.Document(); if (preferredUsername != default) document["preferredUsername"] = preferredUsername; document["webfinger"] = $"acct:{preferredUsername ?? actor.Name}@{new Uri(_harness.Peer.B).Authority}"; _harness.Peer.Serve(new Uri(actor.Id).AbsolutePath, document.ToJsonString()); return actor; } void WebFinger(string self) => _harness.Peer.ServeText("/.well-known/webfinger", new JsonObject { ["links"] = new JsonArray(new JsonObject { ["rel"] = "self", ["type"] = "application/activity+json", ["href"] = self }) }.ToJsonString(), "application/jrd+json"); [Fact] public async Task A_handle_on_another_domain_is_kept_once_its_webfinger_agrees() { var actor = Actor("split"); WebFinger(actor.Id); var stored = await _remote.GetActor(actor.Id, refresh: true, TestContext.Current.CancellationToken); Assert.Equal(new Uri(_harness.Peer.B).Authority, stored.Domain); } [Fact] public async Task A_handle_its_webfinger_does_not_confirm_stays_on_the_actors_host() { var actor = Actor("claims"); WebFinger("http://localhost/users/someone-else"); var stored = await _remote.GetActor(actor.Id, refresh: true, TestContext.Current.CancellationToken); Assert.Equal(new Uri(actor.Id).Authority, stored.Domain); } [Fact] public async Task A_renamed_account_is_asked_again() { var actor = Actor("renames"); WebFinger(actor.Id); await _remote.GetActor(actor.Id, refresh: true, TestContext.Current.CancellationToken); Serve(actor, "renamed"); WebFinger("http://localhost/users/someone-else"); // (another cache: the first fetch holds the actor back for a while) var later = new RemoteActorService(Peer.Http(), _harness.Local, new MemoryCache(new MemoryCacheOptions()), _harness.Db, new StaticOptions(new FederationOptions { AllowPrivateNetworks = true, AllowPlainHttp = true })); var stored = await later.GetActor(actor.Id, refresh: true, TestContext.Current.CancellationToken); Assert.Equal("renamed", stored.UserName); Assert.Equal(new Uri(actor.Id).Authority, stored.Domain); } [Fact] public async Task A_persona_blocking_the_handles_server_hides_the_account() { var token = TestContext.Current.CancellationToken; var (_, alice) = await _harness.Persona("alice"); var split = Actor("split"); WebFinger(split.Id); await _remote.GetActor(split.Id, refresh: true, token); var claims = Actor("claims"); WebFinger("http://localhost/users/someone-else"); await new RemoteActorService(Peer.Http(), _harness.Local, new MemoryCache(new MemoryCacheOptions()), _harness.Db, new StaticOptions(new FederationOptions { AllowPrivateNetworks = true, AllowPlainHttp = true })) .GetActor(claims.Id, refresh: true, token); await DB.Default.SaveAsync(new AccountDomainBlock { AvatarId = alice.Id, Domain = new Uri(_harness.Peer.B).Host }, token); Assert.Equal([split.Id], await Hidden.AuthorsHiddenFrom(alice.Id, new[] { split.Id, claims.Id }, forNotifications: false, token)); Assert.Contains(alice.Id, await Hidden.RecipientsHiding(new[] { alice.Id }, split.Id, token)); Assert.Empty(await Hidden.RecipientsHiding(new[] { alice.Id }, claims.Id, token)); } } }