Transfer endpoint metadata from PageActionDescriptor to CompiledPageActionDescriptor (#19061)
Fixes https://github.com/dotnet/aspnetcore/issues/17300
This commit is contained in:
parent
a085554993
commit
e29c495166
|
|
@ -47,6 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the endpoint metadata for this action.
|
/// Gets or sets the endpoint metadata for this action.
|
||||||
|
/// This API is meant for infrastructure and should not be used by application code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IList<object> EndpointMetadata { get; set; }
|
public IList<object> EndpointMetadata { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor)
|
public override Task<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor)
|
||||||
|
=> LoadAsync(actionDescriptor, EndpointMetadataCollection.Empty);
|
||||||
|
|
||||||
|
internal Task<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor, EndpointMetadataCollection endpointMetadata)
|
||||||
{
|
{
|
||||||
if (actionDescriptor == null)
|
if (actionDescriptor == null)
|
||||||
{
|
{
|
||||||
|
|
@ -79,10 +82,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
return compiledDescriptorTask;
|
return compiledDescriptorTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
return cache.GetOrAdd(actionDescriptor, LoadAsyncCore(actionDescriptor));
|
return cache.GetOrAdd(actionDescriptor, LoadAsyncCore(actionDescriptor, endpointMetadata));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CompiledPageActionDescriptor> LoadAsyncCore(PageActionDescriptor actionDescriptor)
|
private async Task<CompiledPageActionDescriptor> LoadAsyncCore(PageActionDescriptor actionDescriptor, EndpointMetadataCollection endpointMetadata)
|
||||||
{
|
{
|
||||||
var viewDescriptor = await Compiler.CompileAsync(actionDescriptor.RelativePath);
|
var viewDescriptor = await Compiler.CompileAsync(actionDescriptor.RelativePath);
|
||||||
var context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type.GetTypeInfo());
|
var context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type.GetTypeInfo());
|
||||||
|
|
@ -110,7 +113,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
routeNames: new HashSet<string>(StringComparer.OrdinalIgnoreCase),
|
routeNames: new HashSet<string>(StringComparer.OrdinalIgnoreCase),
|
||||||
action: compiled,
|
action: compiled,
|
||||||
routes: Array.Empty<ConventionalRouteEntry>(),
|
routes: Array.Empty<ConventionalRouteEntry>(),
|
||||||
conventions: Array.Empty<Action<EndpointBuilder>>(),
|
conventions: new Action<EndpointBuilder>[]
|
||||||
|
{
|
||||||
|
b =>
|
||||||
|
{
|
||||||
|
// Metadata from PageActionDescriptor is less significant than the one discovered from the compiled type.
|
||||||
|
// Consequently, we'll insert it at the beginning.
|
||||||
|
for (var i = endpointMetadata.Count - 1; i >=0; i--)
|
||||||
|
{
|
||||||
|
b.Metadata.Insert(0, endpointMetadata[i]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
createInertEndpoints: false);
|
createInertEndpoints: false);
|
||||||
|
|
||||||
// In some test scenarios there's no route so the endpoint isn't created. This is fine because
|
// In some test scenarios there's no route so the endpoint isn't created. This is fine because
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
var loadedEndpoints = new List<Endpoint>(endpoints);
|
var loadedEndpoints = new List<Endpoint>(endpoints);
|
||||||
for (var j = 0; j < loadedEndpoints.Count; j++)
|
for (var j = 0; j < loadedEndpoints.Count; j++)
|
||||||
{
|
{
|
||||||
var compiled = await _loader.LoadAsync(loadedEndpoints[j].Metadata.GetMetadata<PageActionDescriptor>());
|
var metadata = loadedEndpoints[j].Metadata;
|
||||||
|
var pageActionDescriptor = metadata.GetMetadata<PageActionDescriptor>();
|
||||||
|
|
||||||
|
CompiledPageActionDescriptor compiled;
|
||||||
|
if (_loader is DefaultPageLoader defaultPageLoader)
|
||||||
|
{
|
||||||
|
compiled = await defaultPageLoader.LoadAsync(pageActionDescriptor, endpoint.Metadata);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
compiled = await _loader.LoadAsync(pageActionDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
loadedEndpoints[j] = compiled.Endpoint;
|
loadedEndpoints[j] = compiled.Endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
||||||
{
|
{
|
||||||
// We found an endpoint instance that has a PageActionDescriptor, but not a
|
// We found an endpoint instance that has a PageActionDescriptor, but not a
|
||||||
// CompiledPageActionDescriptor. Update the CandidateSet.
|
// CompiledPageActionDescriptor. Update the CandidateSet.
|
||||||
var compiled = _loader.LoadAsync(page);
|
Task<CompiledPageActionDescriptor> compiled;
|
||||||
|
if (_loader is DefaultPageLoader defaultPageLoader)
|
||||||
|
{
|
||||||
|
compiled = defaultPageLoader.LoadAsync(page, endpoint.Metadata);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
compiled = _loader.LoadAsync(page);
|
||||||
|
}
|
||||||
|
|
||||||
if (compiled.IsCompletedSuccessfully)
|
if (compiled.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
candidates.ReplaceEndpoint(i, compiled.Result.Endpoint, candidate.Values);
|
candidates.ReplaceEndpoint(i, compiled.Result.Endpoint, candidate.Values);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||||
|
{
|
||||||
|
public class AuthMiddlewareUsingRequireAuthTest : IClassFixture<MvcTestFixture<SecurityWebSite.StartupWithRequireAuth>>
|
||||||
|
{
|
||||||
|
public AuthMiddlewareUsingRequireAuthTest(MvcTestFixture<SecurityWebSite.StartupWithRequireAuth> fixture)
|
||||||
|
{
|
||||||
|
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||||
|
Client = factory.CreateDefaultClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) =>
|
||||||
|
builder.UseStartup<SecurityWebSite.StartupWithRequireAuth>();
|
||||||
|
|
||||||
|
public HttpClient Client { get; }
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RequireAuthConfiguredGlobally_AppliesToControllers()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = "Home/Index";
|
||||||
|
var response = await Client.GetAsync(action);
|
||||||
|
|
||||||
|
await AssertAuthorizeResponse(response);
|
||||||
|
|
||||||
|
// We should be able to login with ClaimA alone
|
||||||
|
var authCookie = await GetAuthCookieAsync("LoginClaimA");
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, action);
|
||||||
|
request.Headers.Add("Cookie", authCookie);
|
||||||
|
|
||||||
|
response = await Client.SendAsync(request);
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RequireAuthConfiguredGlobally_AppliesToRazorPages()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var action = "PagesHome";
|
||||||
|
var response = await Client.GetAsync(action);
|
||||||
|
|
||||||
|
await AssertAuthorizeResponse(response);
|
||||||
|
|
||||||
|
// We should be able to login with ClaimA alone
|
||||||
|
var authCookie = await GetAuthCookieAsync("LoginClaimA");
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, action);
|
||||||
|
request.Headers.Add("Cookie", authCookie);
|
||||||
|
|
||||||
|
response = await Client.SendAsync(request);
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AssertAuthorizeResponse(HttpResponseMessage response)
|
||||||
|
{
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.Redirect);
|
||||||
|
Assert.Equal("/Home/Login", response.Headers.Location.LocalPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetAuthCookieAsync(string action)
|
||||||
|
{
|
||||||
|
var response = await Client.PostAsync($"Login/{action}", null);
|
||||||
|
|
||||||
|
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||||
|
Assert.True(response.Headers.Contains("Set-Cookie"));
|
||||||
|
return response.Headers.GetValues("Set-Cookie").FirstOrDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||||
|
using Microsoft.AspNetCore.Authorization.Policy;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace SecurityWebSite
|
||||||
|
{
|
||||||
|
public class StartupWithRequireAuth
|
||||||
|
{
|
||||||
|
// This method gets called by the runtime. Use this method to add services to the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
// Add framework services.
|
||||||
|
services.AddControllersWithViews().SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||||
|
services.AddRazorPages();
|
||||||
|
services.AddAntiforgery();
|
||||||
|
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
|
||||||
|
{
|
||||||
|
options.LoginPath = "/Home/Login";
|
||||||
|
options.LogoutPath = "/Home/Logout";
|
||||||
|
})
|
||||||
|
.AddCookie("Cookie2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app)
|
||||||
|
{
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseEndpoints(endpoints =>
|
||||||
|
{
|
||||||
|
endpoints.MapRazorPages().RequireAuthorization();
|
||||||
|
endpoints.MapDefaultControllerRoute().RequireAuthorization();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue