From 136bfe04cf8c4ccdc47ee82294a041d403707a65 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Sun, 14 Jul 2024 21:33:00 +0200 Subject: [PATCH] Add support for basic auth with manifeset update provider. Goes along with Robust.Cdn 2.0 --- .../Updates/UpdateProviderManifest.cs | 88 +++++++++++-------- .../UpdateProviderManifestConfiguration.cs | 9 +- 2 files changed, 61 insertions(+), 36 deletions(-) diff --git a/SS14.Watchdog/Components/Updates/UpdateProviderManifest.cs b/SS14.Watchdog/Components/Updates/UpdateProviderManifest.cs index e4d2ec9..e12d34b 100644 --- a/SS14.Watchdog/Components/Updates/UpdateProviderManifest.cs +++ b/SS14.Watchdog/Components/Updates/UpdateProviderManifest.cs @@ -3,8 +3,10 @@ using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Net.Http.Json; using System.Security.Cryptography; +using System.Text; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; @@ -18,16 +20,18 @@ namespace SS14.Watchdog.Components.Updates public sealed class UpdateProviderManifest : UpdateProvider { private const int DownloadTimeoutSeconds = 120; - + private readonly HttpClient _httpClient = new(); private readonly string _manifestUrl; + private readonly UpdateProviderManifestConfiguration _configuration; private readonly ILogger _logger; public UpdateProviderManifest( UpdateProviderManifestConfiguration configuration, ILogger logger) { + _configuration = configuration; _logger = logger; _manifestUrl = configuration.ManifestUrl; } @@ -36,22 +40,9 @@ public override async Task CheckForUpdateAsync( string? currentVersion, CancellationToken cancel = default) { - ManifestInfo? manifest; - try - { - manifest = await _httpClient.GetFromJsonAsync(_manifestUrl, cancel); - } - catch (Exception e) - { - _logger.LogError(e, "Failed to fetch build manifest!"); - return false; - } - + var manifest = await FetchManifestInfoAsync(cancel); if (manifest == null) - { - _logger.LogError("Failed to fetch build manifest: JSON response was null!"); return false; - } return SelectMaxVersion(manifest) != currentVersion; } @@ -61,22 +52,9 @@ public override async Task CheckForUpdateAsync( string binPath, CancellationToken cancel = default) { - ManifestInfo? manifest; - try - { - manifest = await _httpClient.GetFromJsonAsync(_manifestUrl, cancel); - } - catch (Exception e) - { - _logger.LogError(e, "Failed to fetch build manifest!"); - return null; - } - + var manifest = await FetchManifestInfoAsync(cancel); if (manifest == null) - { - _logger.LogError("Failed to fetch build manifest: JSON response was null!"); return null; - } var maxVersion = SelectMaxVersion(manifest); if (maxVersion == null) @@ -84,7 +62,7 @@ public override async Task CheckForUpdateAsync( _logger.LogWarning("There are no versions, not updating"); return null; } - + if (maxVersion == currentVersion) { _logger.LogDebug("Update not necessary!"); @@ -92,7 +70,7 @@ public override async Task CheckForUpdateAsync( } var versionInfo = manifest.Builds[maxVersion]; - + _logger.LogTrace("New version is {NewVersion} from {OldVersion}", maxVersion, currentVersion ?? ""); var rid = RidUtility.FindBestRid(versionInfo.Server.Keys); @@ -106,7 +84,7 @@ public override async Task CheckForUpdateAsync( var build = versionInfo.Server[rid]; var downloadUrl = build.Url; var downloadHash = Convert.FromHexString(build.Sha256); - + // Create temporary file to download binary into (not doing this in memory). await using var tempFile = TempFile.CreateTempFile(); @@ -116,7 +94,13 @@ public override async Task CheckForUpdateAsync( var timeout = Task.Delay(TimeSpan.FromSeconds(DownloadTimeoutSeconds), cancel); var downloadTask = Task.Run(async () => { - var resp = await _httpClient.GetAsync(downloadUrl, cancel); + using var resp = await _httpClient.SendAsync( + MakeAuthenticatedGet(downloadUrl), + HttpCompletionOption.ResponseHeadersRead, + cancel); + + resp.EnsureSuccessStatusCode(); + await resp.Content.CopyToAsync(tempFile, cancel); }, cancel); @@ -139,7 +123,7 @@ public override async Task CheckForUpdateAsync( _logger.LogError("Hash verification failed while updating!"); return null; } - + _logger.LogTrace("Deleting old bin directory ({BinPath})", binPath); if (Directory.Exists(binPath)) { @@ -149,13 +133,47 @@ public override async Task CheckForUpdateAsync( Directory.CreateDirectory(binPath); _logger.LogTrace("Extracting zip file"); - + tempFile.Seek(0, SeekOrigin.Begin); DoBuildExtract(tempFile, binPath); return maxVersion; } + private async Task FetchManifestInfoAsync(CancellationToken cancel) + { + try + { + using var resp = await _httpClient.SendAsync(MakeAuthenticatedGet(_manifestUrl), cancel); + resp.EnsureSuccessStatusCode(); + + var manifest = await resp.Content.ReadFromJsonAsync(cancel); + if (manifest == null) + _logger.LogError("Failed to fetch build manifest: JSON response was null!"); + + return manifest; + } + catch (Exception e) + { + _logger.LogError(e, "Failed to fetch build manifest!"); + return null; + } + } + + private HttpRequestMessage MakeAuthenticatedGet(string url) + { + var message = new HttpRequestMessage(HttpMethod.Get, url); + if (_configuration.Authentication is { Username: { } user, Password: { } pass }) + message.Headers.Authorization = new AuthenticationHeaderValue("Basic", MakeBasicAuthValue(user, pass)); + + return message; + } + + private static string MakeBasicAuthValue(string user, string pass) + { + return Convert.ToBase64String(Encoding.UTF8.GetBytes($"{user}:{pass}")); + } + private static string? SelectMaxVersion(ManifestInfo manifest) { if (manifest.Builds.Count == 0) diff --git a/SS14.Watchdog/Configuration/Updates/UpdateProviderManifestConfiguration.cs b/SS14.Watchdog/Configuration/Updates/UpdateProviderManifestConfiguration.cs index 889b52f..1fd0c7b 100644 --- a/SS14.Watchdog/Configuration/Updates/UpdateProviderManifestConfiguration.cs +++ b/SS14.Watchdog/Configuration/Updates/UpdateProviderManifestConfiguration.cs @@ -3,5 +3,12 @@ public class UpdateProviderManifestConfiguration { public string ManifestUrl { get; set; } = null!; + public ManifestAuthenticationConfiguration? Authentication { get; set; } } -} \ No newline at end of file + + public sealed class ManifestAuthenticationConfiguration + { + public string? Username { get; set; } + public string? Password { get; set; } + } +}