From 90549fd2bce62723c35cc7d40551f1c5f2d2ba9d Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 11 Mar 2025 07:41:46 +0100 Subject: Synapse admin tooling, well known res work --- LibMatrix | 2 +- MatrixUtils.Web/MatrixUtils.Web.csproj | 1 + MatrixUtils.Web/Pages/Dev/WellKnownRes.razor | 67 ++++-- MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor | 3 +- .../Pages/HSAdmin/Synapse/BackgroundJobs.razor | 29 +++ .../Pages/HSAdmin/Synapse/BlockMedia.razor | 191 ++++++++++++++++ .../Pages/HSAdmin/Synapse/BlockMedia.razor.css | 0 .../Components/SynapseRoomShutdownWindow.razor | 5 + .../SynapseRoomShutdownWindowContent.razor | 113 ++++++++++ .../Pages/HSAdmin/Synapse/RoomQuery.razor | 241 ++++++++++++++++++++- .../Pages/Tools/Moderation/MembershipHistory.razor | 11 +- MatrixUtils.Web/Program.cs | 2 +- 12 files changed, 634 insertions(+), 31 deletions(-) create mode 100644 MatrixUtils.Web/Pages/HSAdmin/Synapse/BackgroundJobs.razor create mode 100644 MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor create mode 100644 MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor.css create mode 100644 MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindow.razor create mode 100644 MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor diff --git a/LibMatrix b/LibMatrix index 49bb14a..9659093 160000 --- a/LibMatrix +++ b/LibMatrix @@ -1 +1 @@ -Subproject commit 49bb14a7f0906b6e88b7613ac1bc508d1709c06d +Subproject commit 9659093fcd9745f7030418998ca1cf886ff820b3 diff --git a/MatrixUtils.Web/MatrixUtils.Web.csproj b/MatrixUtils.Web/MatrixUtils.Web.csproj index acb4054..aa9f37f 100644 --- a/MatrixUtils.Web/MatrixUtils.Web.csproj +++ b/MatrixUtils.Web/MatrixUtils.Web.csproj @@ -41,6 +41,7 @@ + diff --git a/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor index 36fa247..9dc9556 100644 --- a/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor +++ b/MatrixUtils.Web/Pages/Dev/WellKnownRes.razor @@ -1,6 +1,7 @@ @page "/Dev/WellKnownRes" @using ArcaneLibs.Extensions -@using LibMatrix.Services.WellKnownResolvers +@using LibMatrix.Services.WellKnownResolver +@using LibMatrix.Services.WellKnownResolver.WellKnownResolvers @inject HomeserverResolverService legacyResolver @inject WellKnownResolverService rewriteResolver @inject ClientWellKnownResolver rewriteClientResolver @@ -9,18 +10,39 @@ Room ID: Execute +Stats:
+Server count: @entries.Count
+Client server resolution rate (N/O/T): @entries.Count(x => x.HasClientWellKnown)/@entries.Count(x => !string.IsNullOrWhiteSpace(x.LegacyResolutionResult?.Client))/@entries.Count +
+Server server resolution rate (N/T): @entries.Count(x => x.HasServerWellKnown)/@entries.Count
+Support resolution rate (N/T): @entries.Count(x => x.HasSupportWellKnown)/@entries.Count
- - + + + @foreach (var entry in entries) { - + - - + + + + + + }
HomeserverLegacy client URLRewrite client URLClient APIServer APIHas support record
@entry.Homeserver@(entry.LegacyClientUrl ?? "null")@(entry.RewriteClientUrl ?? "null") + L: @entry.LegacyResolutionResult?.Client
+ R: @entry.WellKnownResolutionResult?.ClientWellKnown?.Content?.Homeserver.BaseUrl +
+ L: @entry.LegacyResolutionResult?.Server
+ R: @entry.WellKnownResolutionResult?.ServerWellKnown?.Content?.Homeserver +
@(entry.HasSupportWellKnown ? "Y" : "X")
+
+
@(entry.WellKnownResolutionResult?.ToJson() ?? "null")
+
+
@@ -44,8 +66,12 @@ private class TableEntry { public required string Homeserver { get; set; } - public string? LegacyClientUrl { get; set; } - public string? RewriteClientUrl { get; set; } + public HomeserverResolverService.WellKnownUris? LegacyResolutionResult { get; set; } + public WellKnownResolverService.WellKnownRecords? WellKnownResolutionResult { get; set; } + + public bool HasClientWellKnown => WellKnownResolutionResult?.ClientWellKnown is { Content.Homeserver.BaseUrl: { Length: > 0 } }; + public bool HasServerWellKnown => WellKnownResolutionResult?.ServerWellKnown is { Content.Homeserver.Length: > 0 }; + public bool HasSupportWellKnown => WellKnownResolutionResult?.SupportWellKnown?.Content is not null and not { SupportPage: null, Contacts: null or { Count: 0 } }; } private async Task Execute() { @@ -55,7 +81,7 @@ foreach (var homeserver in homeservers) { var e = new TableEntry() { Homeserver = homeserver }; _ = TryResolveLegacy(e); - _ = TryResolveRewrite(e); + _ = TryFullResolveRewrite(e); entries.Add(e); } @@ -65,23 +91,32 @@ private async Task TryResolveLegacy(TableEntry entry) { try { - entry.LegacyClientUrl = (await legacyResolver.ResolveHomeserverFromWellKnown(entry.Homeserver, enableServer: false)).Client; + var cTask = legacyResolver.ResolveHomeserverFromWellKnown(entry.Homeserver, enableServer: false); + var sTask = legacyResolver.ResolveHomeserverFromWellKnown(entry.Homeserver, enableClient: false); + entry.LegacyResolutionResult = (await cTask); + entry.LegacyResolutionResult.Server = (await sTask).Server; StateHasChanged(); } catch { } } - private async Task TryResolveRewrite(TableEntry entry) { + private async Task TryFullResolveRewrite(TableEntry entry) { try { - entry.RewriteClientUrl = (await rewriteClientResolver.TryResolveClientWellKnown(entry.Homeserver)).WellKnown.Homeserver.BaseUrl; + entry.WellKnownResolutionResult = await rewriteResolver.TryResolveWellKnownRecords(entry.Homeserver); StateHasChanged(); } catch { } } - - private string GetColor(TableEntry entry) { - if (entry.LegacyClientUrl == entry.RewriteClientUrl && entry.RewriteClientUrl == null) return "#333333"; - if (entry.LegacyClientUrl == entry.RewriteClientUrl?.TrimEnd('/')) return "#008800"; + + private string GetClientColor(TableEntry entry) { + if (entry.LegacyResolutionResult?.Client == entry.WellKnownResolutionResult?.ClientWellKnown?.Content?.Homeserver?.BaseUrl && entry.WellKnownResolutionResult?.ClientWellKnown?.Content?.Homeserver?.BaseUrl == null) return "#333333"; + if (entry.LegacyResolutionResult?.Client == entry.WellKnownResolutionResult?.ClientWellKnown?.Content?.Homeserver?.BaseUrl?.TrimEnd('/')) return "#008800"; + return "#ff0000"; + } + + private string GetServerColor(TableEntry entry) { + if (entry.LegacyResolutionResult?.Server == entry.WellKnownResolutionResult?.ServerWellKnown?.Content?.Homeserver && entry.WellKnownResolutionResult?.ServerWellKnown?.Content?.Homeserver == null) return "#333333"; + if (entry.LegacyResolutionResult?.Server == entry.WellKnownResolutionResult?.ServerWellKnown?.Content?.Homeserver.TrimEnd('/')) return "#008800"; return "#ff0000"; } diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor index 09cc2cd..6ccdce4 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor @@ -10,7 +10,8 @@ else { @if (Homeserver is AuthenticatedHomeserverSynapse) {

Synapse tools


- Query rooms + Query rooms
+ Block media } else if (Homeserver is AuthenticatedHomeserverHSE) {

Rory&::LibMatrix.HomeserverEmulator tools

diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BackgroundJobs.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BackgroundJobs.razor new file mode 100644 index 0000000..02bf88f --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BackgroundJobs.razor @@ -0,0 +1,29 @@ +@page "/HSAdmin/Synapse/BackgroundJobs" +@using ArcaneLibs.Extensions +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses + +

Homeserver Administration - Background jobs

+
@BackgroundJobStatus?.ToJson(ignoreNull: true)
+ +@code { + private AuthenticatedHomeserverSynapse? Homeserver { get; set; } + private SynapseAdminBackgroundUpdateStatusResponse? BackgroundJobStatus { get; set; } + + protected override async Task OnInitializedAsync() { + var hs = await RmuStorage.GetCurrentSessionOrNavigate() as AuthenticatedHomeserverSynapse; + if (hs is null) return; + Homeserver = hs; + + while (true) { + try { + BackgroundJobStatus = await hs.Admin.GetBackgroundUpdatesStatusAsync(); + StateHasChanged(); + await Task.Delay(1000); + } + catch (Exception ex) { + Console.WriteLine(ex); + } + } + } + +} diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor new file mode 100644 index 0000000..6de4e4d --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor @@ -0,0 +1,191 @@ +@page "/HSAdmin/Synapse/BlockMedia" +@using System.Text.Json +@using ArcaneLibs.Extensions +@using LibMatrix +@using LibMatrix.EventTypes.Spec + +

Homeserver Administration - Block media

+@if (Homeserver is not null) { + + +
+ +
+ @(string.IsNullOrEmpty(EventJson) ? "" : "{ ... }") + +
+
+ + +
+ + +
+
@MxcUri?.ToJson(ignoreNull: true)
+ + @if (Event is not null) { + Redact all messages + } + + @if (Event?.Sender?.Split(':', 2)[1] == Homeserver?.ServerName) { +

User is a local user!

+ Deactivate User + Quarantine all media + } +} + + + +@code { + + private AuthenticatedHomeserverSynapse? Homeserver { get; set; } + + protected override async Task OnInitializedAsync() { + var hs = await RmuStorage.GetCurrentSessionOrNavigate() as AuthenticatedHomeserverSynapse; + if (hs is null) return; + Homeserver = hs; + + if (!string.IsNullOrWhiteSpace(EventUrl)) { + _ = ExpandEventUrl(); + } + } + + [SupplyParameterFromQuery] + public string? EventUrl { + get; + set { + field = value?.Split('?')[0]; + _ = ExpandEventUrl(); + } + } + + private StateEventResponse? Event { get; set; } + + private string? EventJson { + get; + set { + field = value; + _ = ExpandEventJson(); + } + } + + private string? MxcUrl { + get; + set { + field = value; + _ = ExpandMxcUri(); + } + } + + private MxcUri? MxcUri { get; set; } + + private string? RoomId { + get => Event?.RoomId ?? field; + set; + } + + private async Task ExpandEventUrl() { + Console.WriteLine("Expanding event URL..."); + if (!string.IsNullOrWhiteSpace(EventUrl)) { + Console.WriteLine("Parsing event URL..."); + var data = ParseEventUrl(EventUrl); + Console.WriteLine($"Room: {data.room}, Event: {data.eventId}"); + RoomId = data.room; + var room = Homeserver.GetRoom(data.room); + var eventResponse = await room.GetEventAsync(data.eventId); + eventResponse.RoomId ??= data.room; + EventJson = eventResponse?.ToJson() ?? "null"; + } + + StateHasChanged(); + } + + private async Task ExpandEventJson() { + Console.WriteLine("Expanding event JSON..."); + if (!string.IsNullOrWhiteSpace(EventJson)) { + Event = JsonSerializer.Deserialize(EventJson); + MxcUrl = Event?.ContentAs()?.Url; + Console.WriteLine($"MXC URL: {MxcUrl}"); + + var possiblyRelated = await Homeserver.Admin.GetRoomMediaAsync(Event!.RoomId!); + } + + StateHasChanged(); + } + + private async Task ExpandMxcUri() { + Console.WriteLine("Expanding MXC URI..."); + if (!string.IsNullOrWhiteSpace(MxcUrl)) { + MxcUri = MxcUrl; + } + + StateHasChanged(); + } + + private (string room, string eventId) ParseEventUrl(string url) { + var parts = url.Split('/'); + Console.WriteLine($"Parts: {string.Join(", ", parts)}"); + return (parts[4].UrlDecode(), parts[5].Split('?')[0].UrlDecode()); + } + +#region Local user + + private async Task DeactivateUser() { + await Homeserver.Admin.DeactivateUserAsync(Event.Sender, true); + } + + private async Task QuarantineMediaByUser() { + if (Event is null) return; + var media = Homeserver.Admin.GetUserMediaEnumerableAsync(Event?.Sender!); + await foreach (var m in media) { + if (m is not null) { + // await Homeserver.Admin.QuarantineMedia(m); + // await Homeserver.Admin.DeleteMedia(m); + } + } + } + +#endregion + + private async Task RedactAllEvents() { + if (Event is null) return; + await Homeserver!.Admin.DeleteAllMessages(Event.Sender!); + } + +} diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor.css b/MatrixUtils.Web/Pages/HSAdmin/Synapse/BlockMedia.razor.css new file mode 100644 index 0000000..e69de29 diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindow.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindow.razor new file mode 100644 index 0000000..d598994 --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindow.razor @@ -0,0 +1,5 @@ +

SynapseRoomShutdownWindow

+ +@code { + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor new file mode 100644 index 0000000..d5daf75 --- /dev/null +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/Components/SynapseRoomShutdownWindowContent.razor @@ -0,0 +1,113 @@ +@using LibMatrix.Homeservers.Extensions.NamedCaches +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests + +@if (string.IsNullOrWhiteSpace(Context.DeleteId)) { + Media options +
+
+ Quarantine local media: + +
+ Quarantine remote media: + +
+ Delete remote media: + +
+ + User options +
+
+ Suspend local users: + +
+ Quarantine ALL local user media: + +
+ Delete ALL local user media: + +
+ + Room deletion options +
+
+ Block room: + +
+ Purge room: + +
+ Force purge room (unsafe): + +
+ Warning room User ID (optional): + +
+ @if (!string.IsNullOrWhiteSpace(Context.DeleteRequest.NewRoomUserId)) { + Warning room name: + +
+ Warning room message (plaintext): + +
+ } + + Execute +} + +@code { + + [Parameter] + public required RoomShutdownContext Context { get; set; } + + [Parameter] + public required AuthenticatedHomeserverSynapse Homeserver { get; set; } + + private NamedCache TaskMap { get; set; } = null!; + + protected override async Task OnInitializedAsync() { + TaskMap = new NamedCache(Homeserver, "gay.rory.matrixutils.synapse_room_shutdown_tasks"); + } + + public class RoomShutdownContext { + public required string RoomId { get; set; } + public string? DeleteId { get; set; } + public ExtraDeleteOptions ExtraOptions { get; set; } = new(); + + public SynapseAdminRoomDeleteRequest DeleteRequest { get; set; } = new() { + Block = true, + Purge = true, + ForcePurge = false + }; + + public class ExtraDeleteOptions { + // room options + public bool QuarantineLocalMedia { get; set; } + public bool QuarantineRemoteMedia { get; set; } + + public bool DeleteRemoteMedia { get; set; } + + // user options + public bool SuspendLocalUsers { get; set; } + public bool QuarantineLocalUserMedia { get; set; } + public bool DeleteLocalUserMedia { get; set; } + } + } + + public async Task OnComplete() { + await OnCompleteLock.WaitAsync(); + try { + await TaskMap.RemoveValueAsync(Context.DeleteId!); + } + finally { + OnCompleteLock.Release(); + } + } + + public async Task DeleteRoom() { + await TaskMap.SetValueAsync(Context.RoomId, Context); + } + + private static readonly SemaphoreSlim OnCompleteLock = new(1, 1); + +} \ No newline at end of file diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor index 07af1dc..0a93df8 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/RoomQuery.razor @@ -1,7 +1,10 @@ @page "/HSAdmin/Synapse/RoomQuery" -@using LibMatrix.Responses.Admin -@using LibMatrix.Filters +@using Microsoft.AspNetCore.WebUtilities @using ArcaneLibs.Extensions +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests +@using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Responses +@using MatrixUtils.Web.Pages.HSAdmin.Synapse.Components

Homeserver Administration - Room Query

@@ -48,13 +51,17 @@ Ranges - state events + state events - members + members - local members + local members @@ -98,41 +105,84 @@
}

+

+ Delete room + +

@res.StateEvents state events
- @res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server + @if (res.LocalMembers is null) { + @res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server + } + else { + @res.JoinedMembers members, of which @res.JoinedLocalMembers are on this server: @(string.Join(", ", res.LocalMembers)) + }
Full result data
@res.ToJson(ignoreNull: true)
} +@* *@ +@* @if (DeleteRequest.HasValue) { *@ +@* *@ +@* *@ +@* *@ +@* } *@ + +@* @foreach (var (roomId, status) in DeleteStatuses) { *@ +@* *@ +@*
@status.ToJson()
*@ +@*
*@ +@* } *@ + +@foreach(var (roomId, deleteRequest) in DeleteRequests) { + +}