From 6b64ce81b2584dd1d454ddda623001dd54c2205d Mon Sep 17 00:00:00 2001 From: Rory& Date: Fri, 6 Jun 2025 20:20:35 +0200 Subject: Synapse admin fixes, variou schanges --- LibMatrix | 2 +- MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor | 1 + .../Pages/HSAdmin/Synapse/UserList.razor | 384 ++++++--------------- MatrixUtils.Web/Pages/Rooms/PolicyList.razor | 7 +- .../Pages/Tools/Room/DropPowerlevel.razor | 51 +++ 5 files changed, 154 insertions(+), 291 deletions(-) create mode 100644 MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor diff --git a/LibMatrix b/LibMatrix index e16e9f3..61a69b5 160000 --- a/LibMatrix +++ b/LibMatrix @@ -1 +1 @@ -Subproject commit e16e9f3093fab575f5f9323248e7b19fa6d54566 +Subproject commit 61a69b505eb202fe32345b5af3a80ef601bc6799 diff --git a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor index 03d3ad1..6a301bf 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/HSAdmin.razor @@ -11,6 +11,7 @@ else {

Synapse tools


Query rooms
+ Query users
Block media
View running background jobs
} diff --git a/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor b/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor index 3e38ee2..a2ada30 100644 --- a/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor +++ b/MatrixUtils.Web/Pages/HSAdmin/Synapse/UserList.razor @@ -1,8 +1,6 @@ -@page "/HSAdmin/Synapse/RoomQuery" -@using System.Diagnostics.CodeAnalysis +@page "/HSAdmin/Synapse/UserQuery" @using Microsoft.AspNetCore.WebUtilities @using ArcaneLibs.Extensions -@using LibMatrix @using LibMatrix.EventTypes.Spec.State.RoomInfo @using LibMatrix.Homeservers.Extensions.NamedCaches @using LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Filters @@ -11,7 +9,7 @@ @using MatrixUtils.Web.Pages.HSAdmin.Synapse.Components.RoomQuery @inject ILogger Logger -

Homeserver Administration - Room Query

+

Homeserver Administration - User Query


@@ -27,7 +25,7 @@ Local filtering (slow) - + @* *@
@@ -51,120 +49,45 @@ @* *@ } -@foreach (var room in Results) { +@foreach (var user in Results) {
- @* *@

- @if (!string.IsNullOrWhiteSpace(room.CanonicalAlias)) { - @room.CanonicalAlias - - } - @room.RoomId - @if (!string.IsNullOrWhiteSpace(room.Name)) { - (@room.Name) + @user.Name + @if (!string.IsNullOrWhiteSpace(user.DisplayName)) { + (@user.DisplayName) }
- - @if (!string.IsNullOrWhiteSpace(room.Creator)) { - Created by @room.Creator -
- }

- Delete room - Resync state + Log in + @* Delete room *@ + @* Resync state *@ +

@{ List flags = []; - if (true || room.JoinedLocalMembers > 0) { - flags.Add(room.JoinRules switch { - "public" => "Public", - "invite" => "Invite only", - "knock" => "Knock", - "restricted" => "Restricted", - "knock_restricted" => "Knock + restricted", - // TODO: default? - null => null, - "" => null, - _ => "unknown join rule: " + room.JoinRules - }); - - if (!string.IsNullOrWhiteSpace(room.Encryption)) flags.Add("encrypted"); - if (!room.Federatable) flags.Add("unfederated"); + if (user.IsGuest == true) flags.Add("guest"); + if (user.Admin == true) flags.Add("admin"); + if (user.Deactivated == true) flags.Add("deactivated"); + if (user.Erased == true) flags.Add("erased"); + if (user.ShadowBanned == true) flags.Add("shadow banned"); + if (user.Locked == true) flags.Add("locked"); + if (user.Approved == true) flags.Add("approved"); - flags.Add(room.HistoryVisibility switch { - "world_readable" => "world readable history", - "shared" => "shared history", - "invited" => "history since invite", - "joined" => "history since join", - // TODO: default? - null => null, - "" => null, - _ => "unknown history setting: " + room.HistoryVisibility - }); + if (!string.IsNullOrWhiteSpace(user.UserType)) flags.Add($"type=\"{user.UserType}\""); - flags.Add(room.GuestAccess switch { - "can_join" => "guests allowed", - "forbidden" => null, - // TODO: default? - null => null, - "" => null, - _ => "unknown guest access: " + room.GuestAccess, - }); - - flags = flags.Where(x => x != null).ToList(); - } + flags = flags.Where(x => x != null).ToList(); } @string.Join(", ", flags) - @if (room.JoinedLocalMembers == 0 && flags.Count > 0) { - at the time of leaving - }
- @room.StateEvents state events, room version @(room.Version ?? "1")
- @if (room.TombstoneEvent is not null) { - var tombstoneContent = room.TombstoneEvent.ContentAs()!; - Room is tombstoned! Target room: @tombstoneContent.ReplacementRoom, message: @tombstoneContent.Body
- } - - @{ - var memberSummary = room.MemberSummary; - if (room.LocalMembers is not null) { - memberSummary += $": {string.Join(", ", room.LocalMembers)}"; - } - } - @memberSummary
Full result data -
@room.ToJson(ignoreNull: true)
+
@user.ToJson(ignoreNull: true)
} -@* *@ -@* @if (DeleteRequest.HasValue) { *@ -@* *@ -@* *@ -@* *@ -@* } *@ - -@* @foreach (var (roomId, status) in DeleteStatuses) { *@ -@* *@ -@*
@status.ToJson()
*@ -@*
*@ -@* } *@ - -@foreach (var (roomId, deleteRequest) in DeleteRequests) { - - - -} - - @code { @@ -180,17 +103,11 @@ [SupplyParameterFromQuery(Name = "ascending")] public bool Ascending { get; set; } = true; - private List Results { get; set; } = new(); + private List Results { get; set; } = new(); private AuthenticatedHomeserverSynapse Homeserver { get; set; } = null!; - private SynapseAdminLocalRoomQueryFilter Filter { get; set; } = new(); - - private Dictionary DeleteRequests { get; set; } = []; - - // private Dictionary DeleteStatuses { get; set; } = new(); - - private NamedCache TaskMap { get; set; } = null!; + private SynapseAdminLocalUserQueryFilter Filter { get; set; } = new(); protected override async Task OnInitializedAsync() { var hs = await sessionStore.GetCurrentHomeserver(navigateOnFailure: true); @@ -200,8 +117,6 @@ } Homeserver = synapse; - TaskMap = new NamedCache(Homeserver, "gay.rory.matrixutils.synapse_room_shutdown_tasks"); - DeleteRequests = (await TaskMap.ReadCacheMapAsync()).Where(x => x.Value.DeleteId is not null).ToDictionary(); StateHasChanged(); } @@ -212,59 +127,59 @@ foreach (var (key, value) in QueryHelpers.ParseQuery(new Uri(NavigationManager.Uri).Query)) { switch (key) { - case "RoomIdContains": - Filter.RoomIdContains = value[0]!; - break; - case "NameContains": - Filter.NameContains = value[0]!; - break; - case "CanonicalAliasContains": - Filter.CanonicalAliasContains = value[0]!; - break; - case "VersionContains": - Filter.VersionContains = value[0]!; - break; - case "CreatorContains": - Filter.CreatorContains = value[0]!; - break; - case "EncryptionContains": - Filter.EncryptionContains = value[0]!; - break; - case "JoinRulesContains": - Filter.JoinRulesContains = value[0]!; - break; - case "GuestAccessContains": - Filter.GuestAccessContains = value[0]!; - break; - case "HistoryVisibilityContains": - Filter.HistoryVisibilityContains = value[0]!; - break; - case "Federatable": - Filter.Federatable = bool.Parse(value[0]!); - Filter.CheckFederation = true; - break; - case "Public": - Filter.Public = value[0] == "true"; - Filter.CheckPublic = true; - break; - case "JoinedMembersGreaterThan": - Filter.JoinedMembersGreaterThan = int.Parse(value[0]!); - break; - case "JoinedMembersLessThan": - Filter.JoinedMembersLessThan = int.Parse(value[0]!); - break; - case "JoinedLocalMembersGreaterThan": - Filter.JoinedLocalMembersGreaterThan = int.Parse(value[0]!); - break; - case "JoinedLocalMembersLessThan": - Filter.JoinedLocalMembersLessThan = int.Parse(value[0]!); - break; - case "StateEventsGreaterThan": - Filter.StateEventsGreaterThan = int.Parse(value[0]!); - break; - case "StateEventsLessThan": - Filter.StateEventsLessThan = int.Parse(value[0]!); - break; + // case "RoomIdContains": + // Filter.RoomIdContains = value[0]!; + // break; + // case "NameContains": + // Filter.NameContains = value[0]!; + // break; + // case "CanonicalAliasContains": + // Filter.CanonicalAliasContains = value[0]!; + // break; + // case "VersionContains": + // Filter.VersionContains = value[0]!; + // break; + // case "CreatorContains": + // Filter.CreatorContains = value[0]!; + // break; + // case "EncryptionContains": + // Filter.EncryptionContains = value[0]!; + // break; + // case "JoinRulesContains": + // Filter.JoinRulesContains = value[0]!; + // break; + // case "GuestAccessContains": + // Filter.GuestAccessContains = value[0]!; + // break; + // case "HistoryVisibilityContains": + // Filter.HistoryVisibilityContains = value[0]!; + // break; + // case "Federatable": + // Filter.Federatable = bool.Parse(value[0]!); + // Filter.CheckFederation = true; + // break; + // case "Public": + // Filter.Public = value[0] == "true"; + // Filter.CheckPublic = true; + // break; + // case "JoinedMembersGreaterThan": + // Filter.JoinedMembersGreaterThan = int.Parse(value[0]!); + // break; + // case "JoinedMembersLessThan": + // Filter.JoinedMembersLessThan = int.Parse(value[0]!); + // break; + // case "JoinedLocalMembersGreaterThan": + // Filter.JoinedLocalMembersGreaterThan = int.Parse(value[0]!); + // break; + // case "JoinedLocalMembersLessThan": + // Filter.JoinedLocalMembersLessThan = int.Parse(value[0]!); + // break; + // case "StateEventsGreaterThan": + // Filter.StateEventsGreaterThan = int.Parse(value[0]!); + // break; + // case "StateEventsLessThan": + // Filter.StateEventsLessThan = int.Parse(value[0]!); + // break; case "Execute": execute = true; break; @@ -282,28 +197,11 @@ private async Task Search() { Results.Clear(); - var searchRooms = Homeserver.Admin.SearchRoomsAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", searchTerm: SearchTerm, localFilter: Filter).GetAsyncEnumerator(); + var searchRooms = Homeserver.Admin.SearchUsersAsync(orderBy: OrderBy!, dir: Ascending ? "f" : "b", localFilter: Filter).GetAsyncEnumerator(); while (await searchRooms.MoveNextAsync()) { var room = searchRooms.Current; - var roomInfo = new RoomInfo { - RoomId = room.RoomId, - Name = room.Name, - CanonicalAlias = room.CanonicalAlias, - Creator = room.Creator, - Version = room.Version, - Encryption = room.Encryption, - Federatable = room.Federatable, - Public = room.Public, - JoinRules = room.JoinRules, - GuestAccess = room.GuestAccess, - HistoryVisibility = room.HistoryVisibility, - StateEvents = room.StateEvents, - JoinedMembers = room.JoinedMembers, - JoinedLocalMembers = room.JoinedLocalMembers - }; - - Results.Add(roomInfo); + Results.Add(room); if ((Results.Count <= 200 && Results.Count % 10 == 0) || Results.Count % 1000 == 0) { StateHasChanged(); @@ -314,120 +212,32 @@ StateHasChanged(); - var getLocalMembersTasks = Results - .Where(x => x.JoinedLocalMembers is > 0 and < 100) - .Select(async r => { - var members = (await Homeserver.Admin.GetRoomMembersAsync(r.RoomId)).Members.Where(x => x.EndsWith(":" + Homeserver.ServerName)).ToList(); - r.LocalMembers = members; - } - ); - await Task.WhenAll(getLocalMembersTasks); - - var getTombstoneTasks = Results - .Select(async r => { - var state = await Homeserver.Admin.GetRoomStateAsync(r.RoomId, type: "m.room.tombstone"); - var tombstone = state.Events.FirstOrDefault(x => x is { StateKey: "", Type: "m.room.tombstone" }); - if (tombstone is { } tombstoneEvent) { - r.TombstoneEvent = tombstoneEvent; - } - }); - await Task.WhenAll(getTombstoneTasks); - StateHasChanged(); } - Task DeleteRoom(RoomInfo room) { - DeleteRequests.TryAdd(room.RoomId, new() { RoomId = room.RoomId, RoomDetails = room, DeleteRequest = new() { Block = true, Purge = true, ForcePurge = false } }); - StateHasChanged(); - - return Task.CompletedTask; - } - - // - // private async Task DeleteRoom() { - // if (DeleteRequest is { } deleteRequest) { - // var media = await Homeserver.Admin.GetRoomMediaAsync(deleteRequest.RoomId); - // if (deleteRequest.DeleteRequest.QuarantineRemoteMedia) { - // foreach (var remoteMedia in media.Remote) { - // await Homeserver.Admin.QuarantineMediaById(remoteMedia); - // } - // } - // - // if (deleteRequest.DeleteRequest.DeleteRemoteMedia) { - // foreach (var remoteMedia in media.Remote) { - // await Homeserver.Admin.DeleteMediaById(remoteMedia); - // } - // } - // else if (deleteRequest.DeleteRequest.QuarantineLocalMedia) { - // foreach (var localMedia in media.Local) { - // await Homeserver.Admin.QuarantineMediaById(localMedia); - // } - // } - // - // var deleteId = await Homeserver.Admin.DeleteRoom(deleteRequest.RoomId, deleteRequest.DeleteRequest, waitForCompletion: false); - // DeleteRequest = null; - // List alreadyCleanedUsers = []; - // while (true) { - // var status = await Homeserver.Admin.GetRoomDeleteStatus(deleteId.DeleteId); - // DeleteStatuses[deleteRequest.RoomId] = status; - // StateHasChanged(); - // await Task.Delay(5000); - // if (status.Status == "complete") { - // DeleteStatuses.Remove(deleteRequest.RoomId); - // StateHasChanged(); - // break; - // } - // - // if (status.Status == "failed") { - // deleteId = await Homeserver.Admin.DeleteRoom(deleteRequest.RoomId, deleteRequest.DeleteRequest, waitForCompletion: false); - // } - // - // var newCleanedUsers = status.ShutdownRoom?.KickedUsers?.Except(alreadyCleanedUsers).ToList(); - // if (newCleanedUsers is not null) { - // alreadyCleanedUsers.AddRange(newCleanedUsers); - // foreach (var user in newCleanedUsers) { - // if (deleteRequest.DeleteRequest.SuspendLocalUsers) { - // // await Homeserver.Admin.(user); - // } - // - // if (deleteRequest.DeleteRequest.QuarantineLocalUserMedia) { - // await Homeserver.Admin.QuarantineMediaByUserId(user); - // } - // - // if (deleteRequest.DeleteRequest.DeleteLocalUserMedia) { - // var userMedia = Homeserver.Admin.GetUserMediaEnumerableAsync(user); - // await foreach (var mediaEntry in userMedia) { - // await Homeserver.Admin.DeleteMediaById(mediaEntry.MediaId); - // } - // } - // } - // } - // } - // } - // } - private readonly Dictionary validOrderBy = new() { - { "name", "Room name" }, - { "canonical_alias", "Main alias address" }, - { "joined_members", "Number of members (reversed)" }, - { "joined_local_members", "Number of local members (reversed)" }, - { "version", "Room version" }, - { "creator", "Creator of the room" }, - { "encryption", "End-to-end encryption algorithm" }, - { "federatable", "Is room federated" }, - { "public", "Visibility in room list" }, - { "join_rules", "Join rules" }, - { "guest_access", "Guest access" }, - { "history_visibility", "Visibility of history" }, - { "state_events", "Number of state events" } + { "name", "User name" }, + { "is_guest", "Guest status" }, + { "admin", "Admin status" }, + { "user_type", "User type" }, + { "deactivated", "Deactivation status" }, + { "shadow_banned", "Shadow banned status" }, + { "displayname", "Display name" }, + { "avatar_url", "Avatar URL" }, + { "creation_ts", "Creation time" }, + { "last_seen_ts", "Last activity" }, }; - private class RoomInfo : SynapseAdminRoomListResult.SynapseAdminRoomListResultRoom { - public List? LocalMembers { get; set; } - public StateEventResponse? TombstoneEvent { get; set; } + private async Task Login(SynapseAdminUserListResult.SynapseAdminUserListResultUser user) { + var loginResult = await Homeserver.Admin.LoginUserAsync(user.Name, TimeSpan.FromDays(1)); + await sessionStore.AddSession(new() { + AccessToken = loginResult.AccessToken, + DeviceId = loginResult.DeviceId, + UserId = loginResult.UserId, + Homeserver = Homeserver.ServerName, + Proxy = Homeserver.Proxy + }); - [field: AllowNull, MaybeNull] - public string MemberSummary => field ??= $"{JoinedMembers} members, of which {JoinedLocalMembers} are on this server"; } } diff --git a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor index 5f70187..96879b8 100644 --- a/MatrixUtils.Web/Pages/Rooms/PolicyList.razor +++ b/MatrixUtils.Web/Pages/Rooms/PolicyList.razor @@ -100,11 +100,11 @@ else { @foreach (var policy in policies.OrderBy(x => x.RawContent?["entity"]?.GetValue())) { @{ - var typedContent = policy.TypedContent!; + var typedContent = policy.TypedContent! as PolicyRuleEventContent; } @foreach (var prop in proxySafeProps ?? Enumerable.Empty()) { if (prop.Name == "Entity") { - @TruncateMxid((string)prop.GetGetMethod()?.Invoke(typedContent, null)!) + @TruncateMxid(typedContent!.Entity) } else { @prop.GetGetMethod()?.Invoke(typedContent, null) @@ -339,7 +339,8 @@ else { private static Dictionary PolicyTypeIds = KnownPolicyTypes .ToDictionary(x => x, x => x.GetCustomAttributes().Select(y => y.EventName).ToArray()); - private static string TruncateMxid(string mxid) { + private static string TruncateMxid(string? mxid) { + if (string.IsNullOrWhiteSpace(mxid)) return mxid; var parts = mxid.Split(':', 2); if (parts[0].Length > 50) parts[0] = parts[0][..50] + "[...]"; diff --git a/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor b/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor new file mode 100644 index 0000000..3f9c141 --- /dev/null +++ b/MatrixUtils.Web/Pages/Tools/Room/DropPowerlevel.razor @@ -0,0 +1,51 @@ +@page "/Tools/Room/DropPowerlevel" +@using ArcaneLibs.Extensions +@using LibMatrix.EventTypes.Spec.State.RoomInfo +

DropPowerlevel

+
+ +User ID:
+Room ID:
+Execute + +
@Result
+ +@code { + private AuthenticatedHomeserverGeneric? Homeserver { get; set; } = null!; + + [Parameter, SupplyParameterFromQuery(Name = "RoomId")] + public string RoomId { get; set; } = ""; + + [Parameter, SupplyParameterFromQuery(Name = "UserId")] + public string UserId { get; set; } = ""; + + private string Result { get; set; } = ""; + + protected override async Task OnInitializedAsync() { + Homeserver = await sessionStore.GetCurrentHomeserver(); + Result = "I am: " + Homeserver.WhoAmI.ToJson() + "\n"; + StateHasChanged(); + } + + private async Task Execute() { + try { + if (Homeserver is not AuthenticatedHomeserverGeneric hs) { + Result = "Not authenticated"; + return; + } + + var room = hs.GetRoom(RoomId); + + var powerlevels = await room.GetPowerLevelsAsync(); + powerlevels.Users.Remove(UserId); + Result = (await room.SendStateEventAsync(RoomPowerLevelEventContent.EventId, powerlevels)).ToJson(); + } + catch (Exception e) { + Result = e.Message; + } + finally { + StateHasChanged(); + } + } + +} \ No newline at end of file -- cgit 1.5.1