From c8e0228e8a6049129d93873736ce0998d342661d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sun, 12 Apr 2026 18:56:27 +0200 Subject: [PATCH 1/5] Add new middlware --- .../TurnierplanVersionMiddleware.cs | 18 ++++++++++++++++++ src/Turnierplan.App/Program.cs | 3 +++ .../Security/ApiKeyAuthenticationHandler.cs | 8 -------- 3 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 src/Turnierplan.App/Middlewares/TurnierplanVersionMiddleware.cs diff --git a/src/Turnierplan.App/Middlewares/TurnierplanVersionMiddleware.cs b/src/Turnierplan.App/Middlewares/TurnierplanVersionMiddleware.cs new file mode 100644 index 00000000..0ed5c7b9 --- /dev/null +++ b/src/Turnierplan.App/Middlewares/TurnierplanVersionMiddleware.cs @@ -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); + } +} diff --git a/src/Turnierplan.App/Program.cs b/src/Turnierplan.App/Program.cs index 34a2396d..070f34b4 100644 --- a/src/Turnierplan.App/Program.cs +++ b/src/Turnierplan.App/Program.cs @@ -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; @@ -83,6 +84,8 @@ app.UseAuthentication(); app.UseAuthorization(); +app.UseMiddleware(); + app.Map(string.Empty, (HttpContext context) => { context.Response.StatusCode = StatusCodes.Status303SeeOther; diff --git a/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs b/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs index beb3b6e2..a4fbcc6f 100644 --- a/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs +++ b/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs @@ -14,11 +14,6 @@ internal sealed class ApiKeyAuthenticationHandler : AuthenticationHandler _secretHasher; @@ -80,9 +75,6 @@ protected override async Task HandleAuthenticateAsync() 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); } } From 03150d365419326d08669ed6368383b8e7af006c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sun, 12 Apr 2026 19:08:06 +0200 Subject: [PATCH 2/5] Adjust adaper --- .../TurnierplanAdapterTest.cs | 5 ++++- src/Turnierplan.Adapter/TurnierplanClient.cs | 21 ++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Turnierplan.Adapter.Test.Functional/TurnierplanAdapterTest.cs b/src/Turnierplan.Adapter.Test.Functional/TurnierplanAdapterTest.cs index df3e5890..02d15d64 100644 --- a/src/Turnierplan.Adapter.Test.Functional/TurnierplanAdapterTest.cs +++ b/src/Turnierplan.Adapter.Test.Functional/TurnierplanAdapterTest.cs @@ -1,4 +1,5 @@ using System.Net; +using System.Text.RegularExpressions; using FluentAssertions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; @@ -14,6 +15,8 @@ using Turnierplan.Core.RoleAssignment; using Turnierplan.Dal; using Xunit; +using Group = Turnierplan.Adapter.Models.Group; +using Match = Turnierplan.Adapter.Models.Match; using MatchType = Turnierplan.Adapter.Enums.MatchType; namespace Turnierplan.Adapter.Test.Functional; @@ -348,7 +351,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 = 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."; diff --git a/src/Turnierplan.Adapter/TurnierplanClient.cs b/src/Turnierplan.Adapter/TurnierplanClient.cs index 114af19a..b7e56fe5 100644 --- a/src/Turnierplan.Adapter/TurnierplanClient.cs +++ b/src/Turnierplan.Adapter/TurnierplanClient.cs @@ -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; @@ -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() { @@ -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, @"^(?\d+\.\d+\.\d+)\.\d+$"); + + 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)) From a6f8c51971d8615733cf6db77fc3df6b5b096ce2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sun, 12 Apr 2026 19:08:12 +0200 Subject: [PATCH 3/5] Set authentication type --- src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs b/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs index a4fbcc6f..737547a7 100644 --- a/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs +++ b/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs @@ -12,6 +12,7 @@ namespace Turnierplan.App.Security; internal sealed class ApiKeyAuthenticationHandler : AuthenticationHandler { + private const string AuthenticationTypeName = "TurnierplanApiKeyAuthentication"; private const string ApiKeyIdHeaderName = "x-api-key"; private const string ApiKeySecretHeaderName = "x-api-key-secret"; @@ -67,7 +68,7 @@ protected override async Task 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)) ]); From c03e2711788e2ef7a2d31698dea2fa9eabd0f465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sun, 12 Apr 2026 19:10:43 +0200 Subject: [PATCH 4/5] Usings --- .../TurnierplanAdapterTest.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Turnierplan.Adapter.Test.Functional/TurnierplanAdapterTest.cs b/src/Turnierplan.Adapter.Test.Functional/TurnierplanAdapterTest.cs index 02d15d64..d2b79e60 100644 --- a/src/Turnierplan.Adapter.Test.Functional/TurnierplanAdapterTest.cs +++ b/src/Turnierplan.Adapter.Test.Functional/TurnierplanAdapterTest.cs @@ -1,5 +1,4 @@ using System.Net; -using System.Text.RegularExpressions; using FluentAssertions; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; @@ -15,8 +14,6 @@ using Turnierplan.Core.RoleAssignment; using Turnierplan.Dal; using Xunit; -using Group = Turnierplan.Adapter.Models.Group; -using Match = Turnierplan.Adapter.Models.Match; using MatchType = Turnierplan.Adapter.Enums.MatchType; namespace Turnierplan.Adapter.Test.Functional; @@ -351,7 +348,7 @@ public async Task Turnierplan_Client_Throws_Exception_When_Version_Does_Not_Matc _ = await client.GetTournament("x"); }; - var actualVersion = Regex.Replace(typeof(TurnierplanClient).Assembly.GetName().Version!.ToString(), @"\.0$", string.Empty); + 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."; From 9a6ed48958e0c56de7823efa06888636f8362cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=B6rner?= Date: Sun, 12 Apr 2026 19:12:07 +0200 Subject: [PATCH 5/5] Always 0 --- src/Turnierplan.Adapter/TurnierplanClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Turnierplan.Adapter/TurnierplanClient.cs b/src/Turnierplan.Adapter/TurnierplanClient.cs index b7e56fe5..d3913dc0 100644 --- a/src/Turnierplan.Adapter/TurnierplanClient.cs +++ b/src/Turnierplan.Adapter/TurnierplanClient.cs @@ -126,7 +126,7 @@ private static string DetermineAdapterVersion() throw new InvalidOperationException("Could not determine Turnierplan.Adapter version from assembly name."); } - var match = Regex.Match(assemblyVersion, @"^(?\d+\.\d+\.\d+)\.\d+$"); + var match = Regex.Match(assemblyVersion, @"^(?\d+\.\d+\.\d+)\.0$"); return match.Success ? match.Groups["Version"].Value