Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -13,7 +13,7 @@ public class AuthenticationStateData
/// <summary>
/// The client-readable claims that describe the <see cref="AuthenticationState.User"/>.
/// </summary>
public IList<KeyValuePair<string, string>> Claims { get; set; } = [];
public IList<ClaimData> Claims { get; set; } = [];

/// <summary>
/// Gets the value that identifies 'Name' claims. This is used when returning the property <see cref="ClaimsIdentity.Name"/>.
Expand Down
45 changes: 45 additions & 0 deletions src/Components/Authorization/src/ClaimData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.Json.Serialization;

namespace Microsoft.AspNetCore.Components.Authorization;

/// <summary>
/// This is a serializable representation of a <see cref="Claim"/> object that only consists of the type and value.
/// </summary>
public readonly struct ClaimData
{
/// <summary>
/// Constructs a new instance of <see cref="ClaimData"/> from a type and value.
/// </summary>
/// <param name="type">The claim type.</param>
/// <param name="value">The claim value</param>
[JsonConstructor]
public ClaimData(string type, string value)
{
Type = type;
Value = value;
}

/// <summary>
/// Constructs a new instance of <see cref="ClaimData"/> from a <see cref="Claim"/> copying only the
/// <see cref="Claim.Type"/> and <see cref="Claim.Value"/> into their corresponding properties.
/// </summary>
/// <param name="claim">The <see cref="Claim"/> to copy from.</param>
public ClaimData(Claim claim)
: this(claim.Type, claim.Value)
{
}

/// <summary>
/// Gets the claim type of the claim. <seealso cref="ClaimTypes"/>.
/// </summary>
public string Type { get; }

/// <summary>
/// Gets the value of the claim.
/// </summary>
public string Value { get; }
}
8 changes: 7 additions & 1 deletion src/Components/Authorization/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
#nullable enable
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.AuthenticationStateData() -> void
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.Claims.get -> System.Collections.Generic.IList<System.Collections.Generic.KeyValuePair<string!, string!>>!
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.Claims.get -> System.Collections.Generic.IList<Microsoft.AspNetCore.Components.Authorization.ClaimData>!
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.Claims.set -> void
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.NameClaimType.get -> string!
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.NameClaimType.set -> void
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.RoleClaimType.get -> string!
Microsoft.AspNetCore.Components.Authorization.AuthenticationStateData.RoleClaimType.set -> void
Microsoft.AspNetCore.Components.Authorization.ClaimData
Microsoft.AspNetCore.Components.Authorization.ClaimData.ClaimData() -> void
Microsoft.AspNetCore.Components.Authorization.ClaimData.ClaimData(string! type, string! value) -> void
Microsoft.AspNetCore.Components.Authorization.ClaimData.ClaimData(System.Security.Claims.Claim! claim) -> void
Microsoft.AspNetCore.Components.Authorization.ClaimData.Type.get -> string!
Microsoft.AspNetCore.Components.Authorization.ClaimData.Value.get -> string!
Original file line number Diff line number Diff line change
Expand Up @@ -51,19 +51,19 @@ public AuthenticationStateSerializationOptions()
{
foreach (var claim in authenticationState.User.Claims)
{
data.Claims.Add(new(claim.Type, claim.Value));
data.Claims.Add(new(claim));
}
}
else
{
if (authenticationState.User.FindFirst(data.NameClaimType) is { } nameClaim)
{
data.Claims.Add(new(nameClaim.Type, nameClaim.Value));
data.Claims.Add(new(nameClaim));
}

foreach (var roleClaim in authenticationState.User.FindAll(data.RoleClaimType))
{
data.Claims.Add(new(roleClaim.Type, roleClaim.Value));
data.Claims.Add(new(roleClaim));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ private static Task<AuthenticationState> DeserializeAuthenticationStateAsync(Aut

return Task.FromResult(
new AuthenticationState(new ClaimsPrincipal(
new ClaimsIdentity(authenticationStateData.Claims.Select(c => new Claim(c.Key, c.Value)),
new ClaimsIdentity(authenticationStateData.Claims.Select(c => new Claim(c.Type, c.Value)),
authenticationType: nameof(DeserializedAuthenticationStateProvider),
nameType: authenticationStateData.NameClaimType,
roleType: authenticationStateData.RoleClaimType))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.Extensions.Options;
using static Microsoft.AspNetCore.Internal.LinkerFlags;

namespace Microsoft.AspNetCore.Components.WebAssembly.Authentication;

Expand All @@ -21,7 +22,10 @@ internal sealed class DeserializedAuthenticationStateProvider : AuthenticationSt
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = $"{nameof(DeserializedAuthenticationStateProvider)} uses the {nameof(PersistentComponentState)} APIs to deserialize the token, which are already annotated.")]
Justification = $"{nameof(DeserializedAuthenticationStateProvider)} uses the {nameof(DynamicDependencyAttribute)} to preserve the necessary members.")]
[DynamicDependency(JsonSerialized, typeof(AuthenticationStateData))]
[DynamicDependency(JsonSerialized, typeof(IList<ClaimData>))]
[DynamicDependency(JsonSerialized, typeof(ClaimData))]
public DeserializedAuthenticationStateProvider(PersistentComponentState state, IOptions<AuthenticationStateDeserializationOptions> options)
{
if (!state.TryTakeFromJson<AuthenticationStateData?>(PersistenceKey, out var authenticationStateData) || authenticationStateData is null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class BasicTestAppServerSiteFixture<TStartup> : AspNetSiteServerFixture w
{
public BasicTestAppServerSiteFixture()
{
ApplicationAssembly = typeof(TStartup).Assembly;
BuildWebHostMethod = TestServer.Program.BuildWebHost<TStartup>;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;

namespace Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;

// REVIEW: Should this be merged into BasicTestAppServerSiteFixture? Is there any case where we wouldn't
// want to trim BasicTestAppServerSiteFixture tests when TestTrimmedOrMultithreadingApps is true?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dotnet/aspnet-blazor-eng I'm curious what everyone thinks about this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that if other tests don't break, we could enable it, but I suspect we wanted to have this isolated. I believe in the past we would test all the apps with trimming during release. I don't know if/when that changed, but I'm ok if we bring that back.

public class TrimmingServerFixture<TStartup> : BasicTestAppServerSiteFixture<TStartup> where TStartup : class
{
public readonly bool TestTrimmedApps = typeof(ToggleExecutionModeServerFixture<>).Assembly
.GetCustomAttributes<AssemblyMetadataAttribute>()
.First(m => m.Key == "Microsoft.AspNetCore.E2ETesting.TestTrimmedOrMultithreadingApps")
.Value == "true";

public TrimmingServerFixture()
{
if (TestTrimmedApps)
{
BuildWebHostMethod = BuildPublishedWebHost;
GetContentRootMethod = GetPublishedContentRoot;
}
}

private static IHost BuildPublishedWebHost(string[] args) =>
Extensions.Hosting.Host.CreateDefaultBuilder(args)
.ConfigureLogging((ctx, lb) =>
{
var sink = new TestSink();
lb.AddProvider(new TestLoggerProvider(sink));
lb.Services.AddSingleton(sink);
})
.ConfigureWebHostDefaults(webHostBuilder =>
{
webHostBuilder.UseStartup<TStartup>();
// Avoid UseStaticAssets or we won't use the trimmed published output.
})
.Build();

private static string GetPublishedContentRoot(Assembly assembly)
{
var contentRoot = Path.Combine(AppContext.BaseDirectory, "trimmed-or-threading", assembly.GetName().Name);

if (!Directory.Exists(contentRoot))
{
throw new DirectoryNotFoundException($"Test is configured to use trimmed outputs, but trimmed outputs were not found in {contentRoot}.");
}

return contentRoot;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests.AuthTests;

public class DefaultAuthenticationStateSerializationOptionsTest
: ServerTestBase<BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>>>
: ServerTestBase<TrimmingServerFixture<RazorComponentEndpointsStartup<App>>>
{
public DefaultAuthenticationStateSerializationOptionsTest(
BrowserFixture browserFixture,
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
TrimmingServerFixture<RazorComponentEndpointsStartup<App>> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
namespace Microsoft.AspNetCore.Components.E2ETests.ServerRenderingTests.AuthTests;

public class ServerRenderedAuthenticationStateTest
: ServerTestBase<BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>>>
: ServerTestBase<TrimmingServerFixture<RazorComponentEndpointsStartup<App>>>
{
public ServerRenderedAuthenticationStateTest(
BrowserFixture browserFixture,
BasicTestAppServerSiteFixture<RazorComponentEndpointsStartup<App>> serverFixture,
TrimmingServerFixture<RazorComponentEndpointsStartup<App>> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
Expand Down
49 changes: 2 additions & 47 deletions src/Components/test/E2ETest/Tests/RemoteAuthenticationTest.cs
Original file line number Diff line number Diff line change
@@ -1,42 +1,24 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Reflection;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using OpenQA.Selenium;
using TestServer;
using Xunit.Abstractions;

namespace Microsoft.AspNetCore.Components.E2ETest.Tests;

public class RemoteAuthenticationTest :
ServerTestBase<BasicTestAppServerSiteFixture<RemoteAuthenticationStartup>>
ServerTestBase<TrimmingServerFixture<RemoteAuthenticationStartup>>
{
public readonly bool TestTrimmedApps = typeof(ToggleExecutionModeServerFixture<>).Assembly
.GetCustomAttributes<AssemblyMetadataAttribute>()
.First(m => m.Key == "Microsoft.AspNetCore.E2ETesting.TestTrimmedOrMultithreadingApps")
.Value == "true";

public RemoteAuthenticationTest(
BrowserFixture browserFixture,
BasicTestAppServerSiteFixture<RemoteAuthenticationStartup> serverFixture,
TrimmingServerFixture<RemoteAuthenticationStartup> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
serverFixture.ApplicationAssembly = typeof(RemoteAuthenticationStartup).Assembly;

if (TestTrimmedApps)
{
serverFixture.BuildWebHostMethod = BuildPublishedWebHost;
serverFixture.GetContentRootMethod = GetPublishedContentRoot;
}
}

[Fact]
Expand All @@ -49,31 +31,4 @@ public void NavigateToLogin_PreservesExtraQueryParams()
var heading = Browser.Exists(By.TagName("h1"));
Browser.Equal("Hello, Jane Doe!", () => heading.Text);
}

private static IHost BuildPublishedWebHost(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging((ctx, lb) =>
{
var sink = new TestSink();
lb.AddProvider(new TestLoggerProvider(sink));
lb.Services.AddSingleton(sink);
})
.ConfigureWebHostDefaults(webHostBuilder =>
{
webHostBuilder.UseStartup<RemoteAuthenticationStartup>();
// Avoid UseStaticAssets or we won't use the trimmed published output.
})
.Build();

private static string GetPublishedContentRoot(Assembly assembly)
{
var contentRoot = Path.Combine(AppContext.BaseDirectory, "trimmed-or-threading", assembly.GetName().Name);

if (!Directory.Exists(contentRoot))
{
throw new DirectoryNotFoundException($"Test is configured to use trimmed outputs, but trimmed outputs were not found in {contentRoot}.");
}

return contentRoot;
}
}
28 changes: 2 additions & 26 deletions src/Components/test/E2ETest/Tests/WebAssemblyPrerenderedTest.cs
Original file line number Diff line number Diff line change
@@ -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.Reflection;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
using Microsoft.AspNetCore.E2ETesting;
Expand All @@ -10,26 +9,15 @@

namespace Microsoft.AspNetCore.Components.E2ETest.Tests;

public class WebAssemblyPrerenderedTest : ServerTestBase<AspNetSiteServerFixture>
public class WebAssemblyPrerenderedTest : ServerTestBase<TrimmingServerFixture<Wasm.Prerendered.Server.Startup>>
{
public WebAssemblyPrerenderedTest(
BrowserFixture browserFixture,
AspNetSiteServerFixture serverFixture,
TrimmingServerFixture<Wasm.Prerendered.Server.Startup> serverFixture,
ITestOutputHelper output)
: base(browserFixture, serverFixture, output)
{
serverFixture.BuildWebHostMethod = Wasm.Prerendered.Server.Program.BuildWebHost;
serverFixture.Environment = AspNetEnvironment.Development;

var testTrimmedApps = typeof(ToggleExecutionModeServerFixture<>).Assembly
.GetCustomAttributes<AssemblyMetadataAttribute>()
.First(m => m.Key == "Microsoft.AspNetCore.E2ETesting.TestTrimmedOrMultithreadingApps")
.Value == "true";

if (testTrimmedApps)
{
serverFixture.GetContentRootMethod = GetPublishedContentRoot;
}
}

[Fact]
Expand All @@ -53,16 +41,4 @@ private void WaitUntilLoaded()
var jsExecutor = (IJavaScriptExecutor)Browser;
Browser.True(() => jsExecutor.ExecuteScript("return window['__aspnetcore__testing__blazor_wasm__started__'];") is not null);
}

private static string GetPublishedContentRoot(Assembly assembly)
{
var contentRoot = Path.Combine(AppContext.BaseDirectory, "trimmed-or-threading", assembly.GetName().Name);

if (!Directory.Exists(contentRoot))
{
throw new DirectoryNotFoundException($"Test is configured to use trimmed outputs, but trimmed outputs were not found in {contentRoot}.");
}

return contentRoot;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)

_ = app.UseEndpoints(endpoints =>
{
endpoints.MapStaticAssets();
// REVIEW: Why doesn't MapStaticAssets search env.ContentRootPath before AppContext.BaseDirectory first to begin with?
var contentRootStaticAssetsPath = Path.Combine(env.ContentRootPath, "Components.TestServer.staticwebassets.endpoints.json");
if (File.Exists(contentRootStaticAssetsPath))
{
endpoints.MapStaticAssets(contentRootStaticAssetsPath);
}
else
{
endpoints.MapStaticAssets();
}

_ = endpoints.MapRazorComponents<TRootComponent>()
.AddAdditionalAssemblies(Assembly.Load("Components.WasmMinimal"))
.AddInteractiveServerRenderMode(options =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseAntiforgery();
app.UseEndpoints(endpoints =>
{
#if !DEBUG
endpoints.MapStaticAssets(Path.Combine("trimmed-or-threading", "Components.TestServer", "Components.TestServer.staticwebassets.endpoints.json"));
#else
endpoints.MapStaticAssets("Components.TestServer.staticwebassets.endpoints.json");
#endif
var contentRootStaticAssetsPath = Path.Combine(env.ContentRootPath, "Components.TestServer.staticwebassets.endpoints.json");
if (File.Exists(contentRootStaticAssetsPath))
{
endpoints.MapStaticAssets(contentRootStaticAssetsPath);
}
else
{
endpoints.MapStaticAssets();
}

endpoints.MapRazorComponents<RemoteAuthenticationApp>()
.AddAdditionalAssemblies(Assembly.Load("Components.WasmRemoteAuthentication"))
.AddInteractiveWebAssemblyRenderMode(options => options.PathPrefix = "/WasmRemoteAuthentication");
Expand Down