From 0d0511e35d9965fc0ea5190ae3347c3d77c3334c Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Mon, 14 Aug 2023 04:09:13 +0200 Subject: Split LibMatrix into separate repo --- LibMatrix/Services/HomeserverProviderService.cs | 96 +++++++++++++++++++++++++ LibMatrix/Services/HomeserverResolverService.cs | 86 ++++++++++++++++++++++ LibMatrix/Services/ServiceInstaller.cs | 29 ++++++++ LibMatrix/Services/TieredStorageService.cs | 13 ++++ 4 files changed, 224 insertions(+) create mode 100644 LibMatrix/Services/HomeserverProviderService.cs create mode 100644 LibMatrix/Services/HomeserverResolverService.cs create mode 100644 LibMatrix/Services/ServiceInstaller.cs create mode 100644 LibMatrix/Services/TieredStorageService.cs (limited to 'LibMatrix/Services') diff --git a/LibMatrix/Services/HomeserverProviderService.cs b/LibMatrix/Services/HomeserverProviderService.cs new file mode 100644 index 0000000..61c449a --- /dev/null +++ b/LibMatrix/Services/HomeserverProviderService.cs @@ -0,0 +1,96 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using LibMatrix.Extensions; +using LibMatrix.Responses; +using Microsoft.Extensions.Logging; + +namespace LibMatrix.Services; + +public class HomeserverProviderService { + private readonly TieredStorageService _tieredStorageService; + private readonly ILogger _logger; + private readonly HomeserverResolverService _homeserverResolverService; + + public HomeserverProviderService(TieredStorageService tieredStorageService, + ILogger logger, HomeserverResolverService homeserverResolverService) { + Console.WriteLine("Homeserver provider service instantiated!"); + _tieredStorageService = tieredStorageService; + _logger = logger; + _homeserverResolverService = homeserverResolverService; + logger.LogDebug( + $"New HomeserverProviderService created with TieredStorageService<{string.Join(", ", tieredStorageService.GetType().GetProperties().Select(x => x.Name))}>!"); + } + + private static Dictionary _authenticatedHomeserverSemaphore = new(); + private static Dictionary _authenticatedHomeServerCache = new(); + + public async Task GetAuthenticatedWithToken(string homeserver, string accessToken, + string? overrideFullDomain = null) { + SemaphoreSlim sem = _authenticatedHomeserverSemaphore.GetOrCreate(homeserver+accessToken, _ => new SemaphoreSlim(1, 1)); + await sem.WaitAsync(); + if (_authenticatedHomeServerCache.ContainsKey(homeserver+accessToken)) { + sem.Release(); + return _authenticatedHomeServerCache[homeserver+accessToken]; + } + + var hs = new AuthenticatedHomeServer(_tieredStorageService, homeserver, accessToken); + hs.FullHomeServerDomain = overrideFullDomain ?? + await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver); + hs._httpClient.Dispose(); + hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) }; + hs._httpClient.Timeout = TimeSpan.FromSeconds(120); + hs._httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + hs.WhoAmI = (await hs._httpClient.GetFromJsonAsync("/_matrix/client/v3/account/whoami"))!; + + _authenticatedHomeServerCache[homeserver+accessToken] = hs; + sem.Release(); + + return hs; + } + + public async Task GetRemoteHomeserver(string homeserver, string? overrideFullDomain = null) { + var hs = new RemoteHomeServer(homeserver); + hs.FullHomeServerDomain = overrideFullDomain ?? + await _homeserverResolverService.ResolveHomeserverFromWellKnown(homeserver); + hs._httpClient.Dispose(); + hs._httpClient = new MatrixHttpClient { BaseAddress = new Uri(hs.FullHomeServerDomain) }; + hs._httpClient.Timeout = TimeSpan.FromSeconds(120); + return hs; + } + + public async Task Login(string homeserver, string user, string password, + string? overrideFullDomain = null) { + var hs = await GetRemoteHomeserver(homeserver, overrideFullDomain); + var payload = new LoginRequest { + Identifier = new() { User = user }, + Password = password + }; + var resp = await hs._httpClient.PostAsJsonAsync("/_matrix/client/v3/login", payload); + var data = await resp.Content.ReadFromJsonAsync(); + return data!; + } + + private class LoginRequest { + [JsonPropertyName("type")] + public string Type { get; set; } = "m.login.password"; + + [JsonPropertyName("identifier")] + public LoginIdentifier Identifier { get; set; } = new(); + + [JsonPropertyName("password")] + public string Password { get; set; } = ""; + + [JsonPropertyName("initial_device_display_name")] + public string InitialDeviceDisplayName { get; set; } = "Rory&::LibMatrix"; + + public class LoginIdentifier { + [JsonPropertyName("type")] + public string Type { get; set; } = "m.id.user"; + + [JsonPropertyName("user")] + public string User { get; set; } = ""; + } + } +} diff --git a/LibMatrix/Services/HomeserverResolverService.cs b/LibMatrix/Services/HomeserverResolverService.cs new file mode 100644 index 0000000..4d3bc46 --- /dev/null +++ b/LibMatrix/Services/HomeserverResolverService.cs @@ -0,0 +1,86 @@ +using System.Text.Json; +using LibMatrix.Extensions; +using Microsoft.Extensions.Logging; + +namespace LibMatrix.Services; + +public class HomeserverResolverService { + private readonly MatrixHttpClient _httpClient = new(); + private readonly ILogger _logger; + + private static readonly Dictionary _wellKnownCache = new(); + private static readonly Dictionary _wellKnownSemaphores = new(); + + public HomeserverResolverService(ILogger logger) { + _logger = logger; + } + + public async Task ResolveHomeserverFromWellKnown(string homeserver) { + var res = await _resolveHomeserverFromWellKnown(homeserver); + if (!res.StartsWith("http")) res = "https://" + res; + if (res.EndsWith(":443")) res = res.Substring(0, res.Length - 4); + return res; + } + + private async Task _resolveHomeserverFromWellKnown(string homeserver) { + if (homeserver is null) throw new ArgumentNullException(nameof(homeserver)); + SemaphoreSlim sem = _wellKnownSemaphores.GetOrCreate(homeserver, _ => new SemaphoreSlim(1, 1)); + await sem.WaitAsync(); + if (_wellKnownCache.ContainsKey(homeserver)) { + sem.Release(); + return _wellKnownCache[homeserver]; + } + + string? result = null; + _logger.LogInformation($"Attempting to resolve homeserver: {homeserver}"); + result ??= await _tryResolveFromClientWellknown(homeserver); + result ??= await _tryResolveFromServerWellknown(homeserver); + result ??= await _tryCheckIfDomainHasHomeserver(homeserver); + + if (result is not null) { + _logger.LogInformation($"Resolved homeserver: {homeserver} -> {result}"); + _wellKnownCache[homeserver] = result; + sem.Release(); + return result; + } + + throw new InvalidDataException($"Failed to resolve homeserver for {homeserver}! Is it online and configured correctly?"); + } + + private async Task _tryResolveFromClientWellknown(string homeserver) { + if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver; + if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/client")) { + var resp = await _httpClient.GetFromJsonAsync($"{homeserver}/.well-known/matrix/client"); + var hs = resp.GetProperty("m.homeserver").GetProperty("base_url").GetString(); + return hs; + } + + _logger.LogInformation("No client well-known..."); + return null; + } + + private async Task _tryResolveFromServerWellknown(string homeserver) { + if (!homeserver.StartsWith("http")) homeserver = "https://" + homeserver; + if (await _httpClient.CheckSuccessStatus($"{homeserver}/.well-known/matrix/server")) { + var resp = await _httpClient.GetFromJsonAsync($"{homeserver}/.well-known/matrix/server"); + var hs = resp.GetProperty("m.server").GetString(); + return hs; + } + + _logger.LogInformation("No server well-known..."); + return null; + } + + private async Task _tryCheckIfDomainHasHomeserver(string homeserver) { + _logger.LogInformation($"Checking if {homeserver} hosts a homeserver..."); + if (await _httpClient.CheckSuccessStatus($"{homeserver}/_matrix/client/versions")) + return homeserver; + _logger.LogInformation("No homeserver on shortname..."); + return null; + } + + private async Task _tryCheckIfSubDomainHasHomeserver(string homeserver, string subdomain) { + homeserver = homeserver.Replace("https://", $"https://{subdomain}."); + return await _tryCheckIfDomainHasHomeserver(homeserver); + } +} diff --git a/LibMatrix/Services/ServiceInstaller.cs b/LibMatrix/Services/ServiceInstaller.cs new file mode 100644 index 0000000..96a1963 --- /dev/null +++ b/LibMatrix/Services/ServiceInstaller.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace LibMatrix.Services; + +public static class ServiceInstaller { + + public static IServiceCollection AddRoryLibMatrixServices(this IServiceCollection services, RoryLibMatrixConfiguration? config = null) { + //Check required services + if (!services.Any(x => x.ServiceType == typeof(TieredStorageService))) + throw new Exception("[MRUCore/DI] No TieredStorageService has been registered!"); + //Add config + if(config is not null) + services.AddSingleton(config); + else { + services.AddSingleton(new RoryLibMatrixConfiguration()); + } + //Add services + services.AddSingleton(); + services.AddSingleton(); + // services.AddScoped(); + return services; + } + + +} + +public class RoryLibMatrixConfiguration { + public string AppName { get; set; } = "Rory&::LibMatrix"; +} diff --git a/LibMatrix/Services/TieredStorageService.cs b/LibMatrix/Services/TieredStorageService.cs new file mode 100644 index 0000000..954a2ce --- /dev/null +++ b/LibMatrix/Services/TieredStorageService.cs @@ -0,0 +1,13 @@ +using LibMatrix.Interfaces.Services; + +namespace LibMatrix.Services; + +public class TieredStorageService { + public IStorageProvider CacheStorageProvider { get; } + public IStorageProvider DataStorageProvider { get; } + + public TieredStorageService(IStorageProvider cacheStorageProvider, IStorageProvider dataStorageProvider) { + CacheStorageProvider = cacheStorageProvider; + DataStorageProvider = dataStorageProvider; + } +} -- cgit 1.4.1