Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ public async Task Turnierplan_Client_Throws_Exception_When_Version_Does_Not_Matc
_ = await client.GetTournament("x");
};

var actualVersion = typeof(TurnierplanClient).Assembly.GetName().Version!.ToString();
var actualVersion = System.Text.RegularExpressions.Regex.Replace(typeof(TurnierplanClient).Assembly.GetName().Version!.ToString(), @"\.0$", string.Empty);
var expectedMessage = sendHeader
? $"Server version '2024.0.0' does not match the Turnierplan.Adapter version '{actualVersion}'."
: "Could not get 'X-Turnierplan-Version' header from response.";
Expand Down
21 changes: 18 additions & 3 deletions src/Turnierplan.Adapter/TurnierplanClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http.Extensions;
using Turnierplan.Adapter.Models;

Expand All @@ -16,9 +17,7 @@ public sealed class TurnierplanClient : IDisposable
private const string ApiKeySecretHeaderName = "X-Api-Key-Secret";
private const string TurnierplanVersionHeaderName = "X-Turnierplan-Version";

private static readonly string __turnierplanAdapterVersion =
typeof(TurnierplanClient).Assembly.GetName().Version?.ToString()
?? throw new InvalidOperationException("Could not determine Turnierplan.Adapter version from assembly name.");
private static readonly string __turnierplanAdapterVersion = DetermineAdapterVersion();

private static readonly JsonSerializerOptions __serializerOptions = new()
{
Expand Down Expand Up @@ -118,6 +117,22 @@ public void Dispose()
}
}

private static string DetermineAdapterVersion()
{
var assemblyVersion = typeof(TurnierplanClient).Assembly.GetName().Version?.ToString();

if (assemblyVersion is null)
{
throw new InvalidOperationException("Could not determine Turnierplan.Adapter version from assembly name.");
}

var match = Regex.Match(assemblyVersion, @"^(?<Version>\d+\.\d+\.\d+)\.0$");

return match.Success
? match.Groups["Version"].Value
: throw new InvalidOperationException("Could not determine Turnierplan.Adapter version from assembly name.");
}

private static void VerifyServerVersion(HttpResponseMessage response)
{
if (!response.Headers.TryGetValues(TurnierplanVersionHeaderName, out var headerValue))
Expand Down
18 changes: 18 additions & 0 deletions src/Turnierplan.App/Middlewares/TurnierplanVersionMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Turnierplan.App.Constants;

namespace Turnierplan.App.Middlewares;

internal sealed class TurnierplanVersionMiddleware(RequestDelegate next)
{
private const string TurnierplanVersionHeaderName = "X-Turnierplan-Version";

public async Task InvokeAsync(HttpContext httpContext)
{
if (httpContext.User.Identity?.IsAuthenticated == true)
{
httpContext.Response.Headers.Append(TurnierplanVersionHeaderName, TurnierplanVersion.Version);
}

await next(httpContext);
}
}
3 changes: 3 additions & 0 deletions src/Turnierplan.App/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Turnierplan.App.Extensions;
using Turnierplan.App.Helpers;
using Turnierplan.App.Mapping;
using Turnierplan.App.Middlewares;
using Turnierplan.App.OpenApi;
using Turnierplan.App.Options;
using Turnierplan.Dal.Extensions;
Expand Down Expand Up @@ -83,6 +84,8 @@
app.UseAuthentication();
app.UseAuthorization();

app.UseMiddleware<TurnierplanVersionMiddleware>();

app.Map(string.Empty, (HttpContext context) =>
{
context.Response.StatusCode = StatusCodes.Status303SeeOther;
Expand Down
11 changes: 2 additions & 9 deletions src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,9 @@ namespace Turnierplan.App.Security;

internal sealed class ApiKeyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private const string AuthenticationTypeName = "TurnierplanApiKeyAuthentication";
private const string ApiKeyIdHeaderName = "x-api-key";
private const string ApiKeySecretHeaderName = "x-api-key-secret";
private const string TurnierplanVersionHeaderName = "x-turnierplan-version";

private static readonly string __turnierplanVersion =
typeof(ApiKeyAuthenticationHandler).Assembly.GetName().Version?.ToString()
?? throw new InvalidOperationException("Could not determine turnierplan.NET version from assembly name.");

private readonly IApiKeyRepository _apiKeyRepository;
private readonly IPasswordHasher<ApiKey> _secretHasher;
Expand Down Expand Up @@ -72,17 +68,14 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()

await _apiKeyRepository.UnitOfWork.SaveChangesAsync();

var identity = new ClaimsIdentity(claims: [
var identity = new ClaimsIdentity(authenticationType: AuthenticationTypeName, claims: [
new Claim(ClaimTypes.PrincipalId, apiKey.PrincipalId.ToString()),
new Claim(ClaimTypes.PrincipalKind, nameof(PrincipalKind.ApiKey))
]);

var principal = new ClaimsPrincipal([ identity ]);
var ticket = new AuthenticationTicket(principal, Scheme.Name);

// Add the version as response header so that the Turnierplan.Adapter, if used, can detect a potential version mismatch
Context.Response.Headers.Append(TurnierplanVersionHeaderName, __turnierplanVersion);

return AuthenticateResult.Success(ticket);
}
}
Loading