From 234ce65c02113ccae36a8f67d5bd5852dadaa57e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 4 Sep 2024 12:57:36 -0700 Subject: [PATCH] [release/8.2] Fix dashboard auth when unsecured (#5532) * Fix dashboard auth when unsecured * Increase playwrite assert timeout and re-enable test * Comment --------- Co-authored-by: James Newton-King --- .../FrontendCompositeAuthenticationHandler.cs | 8 ------ .../UnsecuredAuthenticationHandler.cs | 25 +++++++++++++++++++ .../DashboardWebApplication.cs | 21 ++++++++++++++-- .../Integration/Playwright/AppBarTests.cs | 1 - .../Playwright/PlaywrightFixture.cs | 5 +++- 5 files changed, 48 insertions(+), 12 deletions(-) create mode 100644 src/Aspire.Dashboard/Authentication/UnsecuredAuthenticationHandler.cs diff --git a/src/Aspire.Dashboard/Authentication/FrontendCompositeAuthenticationHandler.cs b/src/Aspire.Dashboard/Authentication/FrontendCompositeAuthenticationHandler.cs index bd72db2c7e..8581c838e2 100644 --- a/src/Aspire.Dashboard/Authentication/FrontendCompositeAuthenticationHandler.cs +++ b/src/Aspire.Dashboard/Authentication/FrontendCompositeAuthenticationHandler.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Security.Claims; using System.Text.Encodings.Web; using Aspire.Dashboard.Authentication.Connection; using Aspire.Dashboard.Configuration; @@ -29,13 +28,6 @@ protected override async Task HandleAuthenticateAsync() parameters: new Dictionary { [AspirePolicyEvaluator.SuppressChallengeKey] = true })); } - var scheme = GetRelevantAuthenticationScheme(); - if (scheme == null) - { - var id = new ClaimsIdentity([new Claim(OtlpAuthorization.OtlpClaimName, bool.FalseString)]); - return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(id), Scheme.Name)); - } - result = await Context.AuthenticateAsync().ConfigureAwait(false); return result; } diff --git a/src/Aspire.Dashboard/Authentication/UnsecuredAuthenticationHandler.cs b/src/Aspire.Dashboard/Authentication/UnsecuredAuthenticationHandler.cs new file mode 100644 index 0000000000..2ebdbbff4a --- /dev/null +++ b/src/Aspire.Dashboard/Authentication/UnsecuredAuthenticationHandler.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Security.Claims; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Options; + +namespace Aspire.Dashboard.Authentication; + +public class UnsecuredAuthenticationHandler : AuthenticationHandler +{ + public UnsecuredAuthenticationHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder) : base(options, logger, encoder) + { + } + + protected override Task HandleAuthenticateAsync() + { + var id = new ClaimsIdentity( + [new Claim(ClaimTypes.NameIdentifier, "Local"), new Claim(FrontendAuthorizationDefaults.UnsecuredClaimName, bool.TrueString)], + FrontendAuthenticationDefaults.AuthenticationSchemeUnsecured); + + return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(id), Scheme.Name))); + } +} diff --git a/src/Aspire.Dashboard/DashboardWebApplication.cs b/src/Aspire.Dashboard/DashboardWebApplication.cs index 75e5c2b3df..a571e534ca 100644 --- a/src/Aspire.Dashboard/DashboardWebApplication.cs +++ b/src/Aspire.Dashboard/DashboardWebApplication.cs @@ -21,6 +21,7 @@ using Aspire.Dashboard.Otlp.Http; using Aspire.Dashboard.Otlp.Storage; using Aspire.Hosting; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; @@ -607,7 +608,7 @@ private static bool IsSameOrNull(Uri frontendUri, Uri? otlpUrl) private static void ConfigureAuthentication(WebApplicationBuilder builder, DashboardOptions dashboardOptions) { var authentication = builder.Services - .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddAuthentication(o => o.DefaultScheme = ConfigureDefaultAuthScheme(dashboardOptions)) .AddScheme(FrontendCompositeAuthenticationDefaults.AuthenticationScheme, o => { }) .AddScheme(OtlpCompositeAuthenticationDefaults.AuthenticationScheme, o => { }) .AddScheme(OtlpApiKeyAuthenticationDefaults.AuthenticationScheme, o => { }) @@ -728,6 +729,9 @@ private static void ConfigureAuthentication(WebApplicationBuilder builder, Dashb options.Cookie.Name = DashboardAuthCookieName; }); break; + case FrontendAuthMode.Unsecured: + authentication.AddScheme(FrontendAuthenticationDefaults.AuthenticationSchemeUnsecured, o => { }); + break; } builder.Services.AddAuthorization(options => @@ -758,13 +762,24 @@ private static void ConfigureAuthentication(WebApplicationBuilder builder, Dashb options.AddPolicy( name: FrontendAuthorizationDefaults.PolicyName, policy: new AuthorizationPolicyBuilder(FrontendCompositeAuthenticationDefaults.AuthenticationScheme) - .RequireClaim(OtlpAuthorization.OtlpClaimName, [bool.FalseString]) + .RequireClaim(FrontendAuthorizationDefaults.UnsecuredClaimName) .Build()); break; default: throw new NotSupportedException($"Unexpected {nameof(FrontendAuthMode)} enum member: {dashboardOptions.Frontend.AuthMode}"); } }); + + // ASP.NET Core authentication needs to have the correct default scheme for the configured frontend auth. + // This is required for ASP.NET Core/SignalR/Blazor to flow the authenticated user from the request and into the dashboard app. + static string ConfigureDefaultAuthScheme(DashboardOptions dashboardOptions) + { + return dashboardOptions.Frontend.AuthMode switch + { + FrontendAuthMode.Unsecured => FrontendAuthenticationDefaults.AuthenticationSchemeUnsecured, + _ => CookieAuthenticationDefaults.AuthenticationScheme + }; + } } public int Run() @@ -804,10 +819,12 @@ public static class FrontendAuthorizationDefaults { public const string PolicyName = "Frontend"; public const string BrowserTokenClaimName = "BrowserTokenClaim"; + public const string UnsecuredClaimName = "UnsecuredTokenClaim"; } public static class FrontendAuthenticationDefaults { public const string AuthenticationSchemeOpenIdConnect = "FrontendOpenIdConnect"; public const string AuthenticationSchemeBrowserToken = "FrontendBrowserToken"; + public const string AuthenticationSchemeUnsecured = "FrontendUnsecured"; } diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/AppBarTests.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/AppBarTests.cs index 44b7dda15e..2fbd1e5e35 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/AppBarTests.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/AppBarTests.cs @@ -17,7 +17,6 @@ public AppBarTests(DashboardServerFixture dashboardServerFixture, PlaywrightFixt } [Fact] - [ActiveIssue("https://github.com/dotnet/aspire/issues/4851")] public async Task AppBar_Change_Theme() { // Arrange diff --git a/tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightFixture.cs b/tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightFixture.cs index d8caac7f0f..944cec5f30 100644 --- a/tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightFixture.cs +++ b/tests/Aspire.Dashboard.Tests/Integration/Playwright/PlaywrightFixture.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Aspire.Workload.Tests; @@ -13,6 +13,9 @@ public class PlaywrightFixture : IAsyncLifetime public async Task InitializeAsync() { + // Default timeout of 5000 ms could time out on slow CI servers. + Assertions.SetDefaultExpectTimeout(15_000); + PlaywrightProvider.DetectAndSetInstalledPlaywrightDependenciesPath(); Browser = await PlaywrightProvider.CreateBrowserAsync(); }