From 37b97d65c0a5262539a5de560e911048166b8bba Mon Sep 17 00:00:00 2001 From: "Emma [it/its]@Rory&" Date: Fri, 5 Apr 2024 18:58:32 +0200 Subject: Fix homeserver resolution, rewrite homeserver initialisation, HSE work --- .../Homeservers/AuthenticatedHomeserverGeneric.cs | 48 ++++----- .../AuthenticatedHomeserverMxApiExtended.cs | 5 +- .../Homeservers/AuthenticatedHomeserverSynapse.cs | 108 ++------------------- LibMatrix/Homeservers/FederationClient.cs | 39 ++------ .../Models/Requests/AdminRoomDeleteRequest.cs | 23 +++++ .../Models/Responses/AdminRoomListingResult.cs | 64 ++++++++++++ .../Synapse/SynapseAdminApiClient.cs | 107 ++++++++++++++++++++ LibMatrix/Homeservers/RemoteHomeServer.cs | 44 +++------ 8 files changed, 242 insertions(+), 196 deletions(-) create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/AdminRoomDeleteRequest.cs create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListingResult.cs create mode 100644 LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs (limited to 'LibMatrix/Homeservers') diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs index 727c0ea..afa6a6c 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverGeneric.cs @@ -14,49 +14,35 @@ using LibMatrix.Responses; using LibMatrix.RoomTypes; using LibMatrix.Services; using LibMatrix.Utilities; +using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Homeservers; -public class AuthenticatedHomeserverGeneric(string serverName, string accessToken) : RemoteHomeserver(serverName) { - public static async Task Create(string serverName, string accessToken, string? proxy = null) where T : AuthenticatedHomeserverGeneric => - await Create(typeof(T), serverName, accessToken, proxy) as T ?? throw new InvalidOperationException($"Failed to create instance of {typeof(T).Name}"); - - public static async Task Create(Type type, string serverName, string accessToken, string? proxy = null) { - if (string.IsNullOrWhiteSpace(proxy)) - proxy = null; - if (!type.IsAssignableTo(typeof(AuthenticatedHomeserverGeneric))) throw new ArgumentException("Type must be a subclass of AuthenticatedHomeserverGeneric", nameof(type)); - var instance = Activator.CreateInstance(type, serverName, accessToken) as AuthenticatedHomeserverGeneric - ?? throw new InvalidOperationException($"Failed to create instance of {type.Name}"); - - instance.ClientHttpClient = new MatrixHttpClient { - Timeout = TimeSpan.FromMinutes(15), - DefaultRequestHeaders = { - Authorization = new AuthenticationHeaderValue("Bearer", accessToken) - } - }; - instance.FederationClient = await FederationClient.TryCreate(serverName, proxy); +public class AuthenticatedHomeserverGeneric : RemoteHomeserver { + public AuthenticatedHomeserverGeneric(string serverName, HomeserverResolverService.WellKnownUris wellKnownUris, ref string? proxy, string accessToken) : base(serverName, + wellKnownUris, ref proxy) { + AccessToken = accessToken; + ClientHttpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - if (string.IsNullOrWhiteSpace(proxy)) { - var urls = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(serverName); - instance.ClientHttpClient.BaseAddress = new Uri(urls?.Client ?? throw new InvalidOperationException("Failed to resolve homeserver")); - } - else { - instance.ClientHttpClient.BaseAddress = new Uri(proxy); - instance.ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", serverName); - } + NamedCaches = new HsNamedCaches(this); + } + + public async Task Initialise() { + WhoAmI = await ClientHttpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"); + } - instance.WhoAmI = await instance.ClientHttpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"); - instance.NamedCaches = new HsNamedCaches(instance); + private WhoAmIResponse? _whoAmI; - return instance; + public WhoAmIResponse WhoAmI { + get => _whoAmI ?? throw new Exception("Initialise was not called or awaited, WhoAmI is null!"); + private set => _whoAmI = value; } - public WhoAmIResponse WhoAmI { get; set; } public string UserId => WhoAmI.UserId; public string UserLocalpart => UserId.Split(":")[0][1..]; public string ServerName => UserId.Split(":", 2)[1]; - public string AccessToken { get; set; } = accessToken; + public string AccessToken { get; set; } public HsNamedCaches NamedCaches { get; set; } = null!; diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs index f0b5675..f55acb8 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverMxApiExtended.cs @@ -1,3 +1,6 @@ +using LibMatrix.Services; + namespace LibMatrix.Homeservers; -public class AuthenticatedHomeserverMxApiExtended(string baseUrl, string accessToken) : AuthenticatedHomeserverGeneric(baseUrl, accessToken); \ No newline at end of file +public class AuthenticatedHomeserverMxApiExtended(string serverName, HomeserverResolverService.WellKnownUris wellKnownUris, ref string? proxy, string accessToken) + : AuthenticatedHomeserverGeneric(serverName, wellKnownUris, ref proxy, accessToken); \ No newline at end of file diff --git a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs index 8df0c5b..307a226 100644 --- a/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs +++ b/LibMatrix/Homeservers/AuthenticatedHomeserverSynapse.cs @@ -1,113 +1,17 @@ using ArcaneLibs.Extensions; using LibMatrix.Filters; +using LibMatrix.Homeservers.ImplementationDetails.Synapse; using LibMatrix.Responses.Admin; +using LibMatrix.Services; namespace LibMatrix.Homeservers; public class AuthenticatedHomeserverSynapse : AuthenticatedHomeserverGeneric { - public readonly SynapseAdminApi Admin; + public readonly SynapseAdminApiClient Admin; - public class SynapseAdminApi(AuthenticatedHomeserverSynapse authenticatedHomeserver) { - public async IAsyncEnumerable SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", - string? searchTerm = null, LocalRoomQueryFilter? localFilter = null) { - AdminRoomListingResult? res = null; - var i = 0; - int? totalRooms = null; - do { - var url = $"/_synapse/admin/v1/rooms?limit={Math.Min(limit, 100)}&dir={dir}&order_by={orderBy}"; - if (!string.IsNullOrEmpty(searchTerm)) url += $"&search_term={searchTerm}"; - - if (res?.NextBatch is not null) url += $"&from={res.NextBatch}"; - - Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---"); - - res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync(url); - totalRooms ??= res.TotalRooms; - Console.WriteLine(res.ToJson(false)); - foreach (var room in res.Rooms) { - if (localFilter is not null) { - if (!room.RoomId.Contains(localFilter.RoomIdContains)) { - totalRooms--; - continue; - } - - if (!room.Name?.Contains(localFilter.NameContains) == true) { - totalRooms--; - continue; - } - - if (!room.CanonicalAlias?.Contains(localFilter.CanonicalAliasContains) == true) { - totalRooms--; - continue; - } - - if (!room.Version.Contains(localFilter.VersionContains)) { - totalRooms--; - continue; - } - - if (!room.Creator.Contains(localFilter.CreatorContains)) { - totalRooms--; - continue; - } - - if (!room.Encryption?.Contains(localFilter.EncryptionContains) == true) { - totalRooms--; - continue; - } - - if (!room.JoinRules?.Contains(localFilter.JoinRulesContains) == true) { - totalRooms--; - continue; - } - - if (!room.GuestAccess?.Contains(localFilter.GuestAccessContains) == true) { - totalRooms--; - continue; - } - - if (!room.HistoryVisibility?.Contains(localFilter.HistoryVisibilityContains) == true) { - totalRooms--; - continue; - } - - if (localFilter.CheckFederation && room.Federatable != localFilter.Federatable) { - totalRooms--; - continue; - } - - if (localFilter.CheckPublic && room.Public != localFilter.Public) { - totalRooms--; - continue; - } - - if (room.JoinedMembers < localFilter.JoinedMembersGreaterThan || room.JoinedMembers > localFilter.JoinedMembersLessThan) { - totalRooms--; - continue; - } - - if (room.JoinedLocalMembers < localFilter.JoinedLocalMembersGreaterThan || room.JoinedLocalMembers > localFilter.JoinedLocalMembersLessThan) { - totalRooms--; - continue; - } - } - // if (contentSearch is not null && !string.IsNullOrEmpty(contentSearch) && - // !( - // room.Name?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true || - // room.CanonicalAlias?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true || - // room.Creator?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true - // ) - // ) { - // totalRooms--; - // continue; - // } - - i++; - yield return room; - } - } while (i < Math.Min(limit, totalRooms ?? limit)); - } + public AuthenticatedHomeserverSynapse(string serverName, HomeserverResolverService.WellKnownUris wellKnownUris, ref string? proxy, string accessToken) : base(serverName, + wellKnownUris, ref proxy, accessToken) { + Admin = new(this); } - public AuthenticatedHomeserverSynapse(string serverName, string accessToken) : base(serverName, accessToken) => Admin = new SynapseAdminApi(this); } \ No newline at end of file diff --git a/LibMatrix/Homeservers/FederationClient.cs b/LibMatrix/Homeservers/FederationClient.cs index 3926b29..dc0d1f6 100644 --- a/LibMatrix/Homeservers/FederationClient.cs +++ b/LibMatrix/Homeservers/FederationClient.cs @@ -1,41 +1,19 @@ using System.Text.Json.Serialization; using LibMatrix.Extensions; using LibMatrix.Services; +using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Homeservers; -public class FederationClient(string baseUrl) { - public static async Task TryCreate(string baseUrl, string? proxy = null) { - try { - return await Create(baseUrl, proxy); - } - catch (Exception e) { - Console.WriteLine($"Failed to create homeserver {baseUrl}: {e.Message}"); - return null; - } +public class FederationClient { + public FederationClient(string federationEndpoint, string? proxy = null) { + HttpClient = new MatrixHttpClient { + BaseAddress = new Uri(proxy?.TrimEnd('/') ?? federationEndpoint.TrimEnd('/')), + Timeout = TimeSpan.FromSeconds(120) + }; + if (proxy is not null) HttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", federationEndpoint); } - public static async Task Create(string baseUrl, string? proxy = null) { - var homeserver = new FederationClient(baseUrl); - homeserver.WellKnownUris = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl); - if (string.IsNullOrWhiteSpace(proxy) && string.IsNullOrWhiteSpace(homeserver.WellKnownUris.Client)) - Console.WriteLine($"Failed to resolve homeserver client URI for {baseUrl}"); - if (string.IsNullOrWhiteSpace(proxy) && string.IsNullOrWhiteSpace(homeserver.WellKnownUris.Server)) - Console.WriteLine($"Failed to resolve homeserver server URI for {baseUrl}"); - - if (!string.IsNullOrWhiteSpace(homeserver.WellKnownUris.Server)) - homeserver.HttpClient = new MatrixHttpClient { - BaseAddress = new Uri(proxy ?? homeserver.WellKnownUris.Server ?? throw new InvalidOperationException($"Failed to resolve homeserver server URI for {baseUrl}")), - Timeout = TimeSpan.FromSeconds(120) - }; - - if (proxy is not null) homeserver.HttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", baseUrl); - - return homeserver; - } - - public string BaseUrl { get; } = baseUrl; - public MatrixHttpClient HttpClient { get; set; } = null!; public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } = null!; @@ -46,7 +24,6 @@ public class ServerVersionResponse { [JsonPropertyName("server")] public required ServerInfo Server { get; set; } - // ReSharper disable once ClassNeverInstantiated.Global public class ServerInfo { [JsonPropertyName("name")] public string Name { get; set; } diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/AdminRoomDeleteRequest.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/AdminRoomDeleteRequest.cs new file mode 100644 index 0000000..f4c927a --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Requests/AdminRoomDeleteRequest.cs @@ -0,0 +1,23 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse.Models.Requests; + +public class AdminRoomDeleteRequest { + [JsonPropertyName("new_room_user_id")] + public string? NewRoomUserId { get; set; } + + [JsonPropertyName("room_name")] + public string? RoomName { get; set; } + + [JsonPropertyName("block")] + public bool Block { get; set; } + + [JsonPropertyName("purge")] + public bool Purge { get; set; } + + [JsonPropertyName("message")] + public string? Message { get; set; } + + [JsonPropertyName("force_purge")] + public bool ForcePurge { get; set; } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListingResult.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListingResult.cs new file mode 100644 index 0000000..7ab96ac --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/Models/Responses/AdminRoomListingResult.cs @@ -0,0 +1,64 @@ +using System.Text.Json.Serialization; + +namespace LibMatrix.Responses.Admin; + +public class AdminRoomListingResult { + [JsonPropertyName("offset")] + public int Offset { get; set; } + + [JsonPropertyName("total_rooms")] + public int TotalRooms { get; set; } + + [JsonPropertyName("next_batch")] + public int? NextBatch { get; set; } + + [JsonPropertyName("prev_batch")] + public int? PrevBatch { get; set; } + + [JsonPropertyName("rooms")] + public List Rooms { get; set; } = new(); + + public class AdminRoomListingResultRoom { + [JsonPropertyName("room_id")] + public required string RoomId { get; set; } + + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("canonical_alias")] + public string? CanonicalAlias { get; set; } + + [JsonPropertyName("joined_members")] + public int JoinedMembers { get; set; } + + [JsonPropertyName("joined_local_members")] + public int JoinedLocalMembers { get; set; } + + [JsonPropertyName("version")] + public string? Version { get; set; } + + [JsonPropertyName("creator")] + public string? Creator { get; set; } + + [JsonPropertyName("encryption")] + public string? Encryption { get; set; } + + [JsonPropertyName("federatable")] + public bool Federatable { get; set; } + + [JsonPropertyName("public")] + public bool Public { get; set; } + + [JsonPropertyName("join_rules")] + public string? JoinRules { get; set; } + + [JsonPropertyName("guest_access")] + public string? GuestAccess { get; set; } + + [JsonPropertyName("history_visibility")] + public string? HistoryVisibility { get; set; } + + [JsonPropertyName("state_events")] + public int StateEvents { get; set; } + } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs new file mode 100644 index 0000000..5cc055d --- /dev/null +++ b/LibMatrix/Homeservers/ImplementationDetails/Synapse/SynapseAdminApiClient.cs @@ -0,0 +1,107 @@ +using ArcaneLibs.Extensions; +using LibMatrix.Filters; +using LibMatrix.Responses.Admin; + +namespace LibMatrix.Homeservers.ImplementationDetails.Synapse; + +public class SynapseAdminApiClient(AuthenticatedHomeserverSynapse authenticatedHomeserver) { + public async IAsyncEnumerable SearchRoomsAsync(int limit = int.MaxValue, string orderBy = "name", string dir = "f", + string? searchTerm = null, LocalRoomQueryFilter? localFilter = null) { + AdminRoomListingResult? res = null; + var i = 0; + int? totalRooms = null; + do { + var url = $"/_synapse/admin/v1/rooms?limit={Math.Min(limit, 100)}&dir={dir}&order_by={orderBy}"; + if (!string.IsNullOrEmpty(searchTerm)) url += $"&search_term={searchTerm}"; + + if (res?.NextBatch is not null) url += $"&from={res.NextBatch}"; + + Console.WriteLine($"--- ADMIN Querying Room List with URL: {url} - Already have {i} items... ---"); + + res = await authenticatedHomeserver.ClientHttpClient.GetFromJsonAsync(url); + totalRooms ??= res.TotalRooms; + Console.WriteLine(res.ToJson(false)); + foreach (var room in res.Rooms) { + if (localFilter is not null) { + if (!room.RoomId.Contains(localFilter.RoomIdContains)) { + totalRooms--; + continue; + } + + if (!room.Name?.Contains(localFilter.NameContains) == true) { + totalRooms--; + continue; + } + + if (!room.CanonicalAlias?.Contains(localFilter.CanonicalAliasContains) == true) { + totalRooms--; + continue; + } + + if (!room.Version.Contains(localFilter.VersionContains)) { + totalRooms--; + continue; + } + + if (!room.Creator.Contains(localFilter.CreatorContains)) { + totalRooms--; + continue; + } + + if (!room.Encryption?.Contains(localFilter.EncryptionContains) == true) { + totalRooms--; + continue; + } + + if (!room.JoinRules?.Contains(localFilter.JoinRulesContains) == true) { + totalRooms--; + continue; + } + + if (!room.GuestAccess?.Contains(localFilter.GuestAccessContains) == true) { + totalRooms--; + continue; + } + + if (!room.HistoryVisibility?.Contains(localFilter.HistoryVisibilityContains) == true) { + totalRooms--; + continue; + } + + if (localFilter.CheckFederation && room.Federatable != localFilter.Federatable) { + totalRooms--; + continue; + } + + if (localFilter.CheckPublic && room.Public != localFilter.Public) { + totalRooms--; + continue; + } + + if (room.JoinedMembers < localFilter.JoinedMembersGreaterThan || room.JoinedMembers > localFilter.JoinedMembersLessThan) { + totalRooms--; + continue; + } + + if (room.JoinedLocalMembers < localFilter.JoinedLocalMembersGreaterThan || room.JoinedLocalMembers > localFilter.JoinedLocalMembersLessThan) { + totalRooms--; + continue; + } + } + // if (contentSearch is not null && !string.IsNullOrEmpty(contentSearch) && + // !( + // room.Name?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true || + // room.CanonicalAlias?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true || + // room.Creator?.Contains(contentSearch, StringComparison.InvariantCultureIgnoreCase) == true + // ) + // ) { + // totalRooms--; + // continue; + // } + + i++; + yield return room; + } + } while (i < Math.Min(limit, totalRooms ?? limit)); + } +} \ No newline at end of file diff --git a/LibMatrix/Homeservers/RemoteHomeServer.cs b/LibMatrix/Homeservers/RemoteHomeServer.cs index 8cd7ad7..e6d58b1 100644 --- a/LibMatrix/Homeservers/RemoteHomeServer.cs +++ b/LibMatrix/Homeservers/RemoteHomeServer.cs @@ -6,50 +6,32 @@ using ArcaneLibs.Extensions; using LibMatrix.Extensions; using LibMatrix.Responses; using LibMatrix.Services; +using Microsoft.Extensions.Logging.Abstractions; namespace LibMatrix.Homeservers; -public class RemoteHomeserver(string baseUrl) { - public static async Task TryCreate(string baseUrl, string? proxy = null) { - try { - return await Create(baseUrl, proxy); - } - catch (Exception e) { - Console.WriteLine($"Failed to create homeserver {baseUrl}: {e.Message}"); - return null; - } - } - - public static async Task Create(string baseUrl, string? proxy = null) { +public class RemoteHomeserver { + public RemoteHomeserver(string baseUrl, HomeserverResolverService.WellKnownUris wellKnownUris, ref string? proxy) { if (string.IsNullOrWhiteSpace(proxy)) proxy = null; - var homeserver = new RemoteHomeserver(baseUrl); - homeserver.WellKnownUris = await new HomeserverResolverService().ResolveHomeserverFromWellKnown(baseUrl); - if (string.IsNullOrWhiteSpace(homeserver.WellKnownUris.Client)) - Console.WriteLine($"Failed to resolve homeserver client URI for {baseUrl}"); - if (string.IsNullOrWhiteSpace(homeserver.WellKnownUris.Server)) - Console.WriteLine($"Failed to resolve homeserver server URI for {baseUrl}"); - - Console.WriteLine(homeserver.WellKnownUris.ToJson(ignoreNull: false)); - - homeserver.ClientHttpClient = new MatrixHttpClient { - BaseAddress = new Uri(proxy ?? homeserver.WellKnownUris.Client ?? throw new InvalidOperationException($"Failed to resolve homeserver client URI for {baseUrl}")), + BaseUrl = baseUrl; + WellKnownUris = wellKnownUris; + ClientHttpClient = new MatrixHttpClient { + BaseAddress = new Uri(proxy?.TrimEnd('/') ?? wellKnownUris.Client?.TrimEnd('/') ?? throw new InvalidOperationException($"No client URI for {baseUrl}!")), Timeout = TimeSpan.FromSeconds(300) }; - homeserver.FederationClient = await FederationClient.TryCreate(baseUrl, proxy); - - if (proxy is not null) homeserver.ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", baseUrl); - - return homeserver; + if (proxy is not null) ClientHttpClient.DefaultRequestHeaders.Add("MXAE_UPSTREAM", baseUrl); + if (!string.IsNullOrWhiteSpace(wellKnownUris.Server)) + FederationClient = new FederationClient(WellKnownUris.Server!, proxy); } private Dictionary _profileCache { get; set; } = new(); - public string BaseUrl { get; } = baseUrl; + public string BaseUrl { get; } - public MatrixHttpClient ClientHttpClient { get; set; } = null!; + public MatrixHttpClient ClientHttpClient { get; set; } public FederationClient? FederationClient { get; set; } - public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } = null!; + public HomeserverResolverService.WellKnownUris WellKnownUris { get; set; } public async Task GetProfileAsync(string mxid, bool useCache = false) { if (mxid is null) throw new ArgumentNullException(nameof(mxid)); -- cgit 1.4.1