Skip to content

Commit

Permalink
Add support for basic auth with manifeset update provider.
Browse files Browse the repository at this point in the history
Goes along with Robust.Cdn 2.0
  • Loading branch information
PJB3005 committed Jul 14, 2024
1 parent cb287ca commit 136bfe0
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 36 deletions.
88 changes: 53 additions & 35 deletions SS14.Watchdog/Components/Updates/UpdateProviderManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<UpdateProviderManifest> _logger;

public UpdateProviderManifest(
UpdateProviderManifestConfiguration configuration,
ILogger<UpdateProviderManifest> logger)
{
_configuration = configuration;
_logger = logger;
_manifestUrl = configuration.ManifestUrl;
}
Expand All @@ -36,22 +40,9 @@ public override async Task<bool> CheckForUpdateAsync(
string? currentVersion,
CancellationToken cancel = default)
{
ManifestInfo? manifest;
try
{
manifest = await _httpClient.GetFromJsonAsync<ManifestInfo>(_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;
}
Expand All @@ -61,38 +52,25 @@ public override async Task<bool> CheckForUpdateAsync(
string binPath,
CancellationToken cancel = default)
{
ManifestInfo? manifest;
try
{
manifest = await _httpClient.GetFromJsonAsync<ManifestInfo>(_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)
{
_logger.LogWarning("There are no versions, not updating");
return null;
}

if (maxVersion == currentVersion)
{
_logger.LogDebug("Update not necessary!");
return null;
}

var versionInfo = manifest.Builds[maxVersion];

_logger.LogTrace("New version is {NewVersion} from {OldVersion}", maxVersion, currentVersion ?? "<none>");

var rid = RidUtility.FindBestRid(versionInfo.Server.Keys);
Expand All @@ -106,7 +84,7 @@ public override async Task<bool> 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();

Expand All @@ -116,7 +94,13 @@ public override async Task<bool> 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);

Expand All @@ -139,7 +123,7 @@ public override async Task<bool> CheckForUpdateAsync(
_logger.LogError("Hash verification failed while updating!");
return null;
}

_logger.LogTrace("Deleting old bin directory ({BinPath})", binPath);
if (Directory.Exists(binPath))
{
Expand All @@ -149,13 +133,47 @@ public override async Task<bool> CheckForUpdateAsync(
Directory.CreateDirectory(binPath);

_logger.LogTrace("Extracting zip file");

tempFile.Seek(0, SeekOrigin.Begin);
DoBuildExtract(tempFile, binPath);

return maxVersion;
}

private async Task<ManifestInfo?> FetchManifestInfoAsync(CancellationToken cancel)
{
try
{
using var resp = await _httpClient.SendAsync(MakeAuthenticatedGet(_manifestUrl), cancel);
resp.EnsureSuccessStatusCode();

var manifest = await resp.Content.ReadFromJsonAsync<ManifestInfo>(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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,12 @@
public class UpdateProviderManifestConfiguration
{
public string ManifestUrl { get; set; } = null!;
public ManifestAuthenticationConfiguration? Authentication { get; set; }
}
}

public sealed class ManifestAuthenticationConfiguration
{
public string? Username { get; set; }
public string? Password { get; set; }
}
}

0 comments on commit 136bfe0

Please sign in to comment.