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>
|
||||
/// Gets or sets the endpoint metadata for this action.
|
||||
/// This API is meant for infrastructure and should not be used by application code.
|
||||
/// </summary>
|
||||
public IList<object> EndpointMetadata { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
}
|
||||
|
||||
public override Task<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor)
|
||||
=> LoadAsync(actionDescriptor, EndpointMetadataCollection.Empty);
|
||||
|
||||
internal Task<CompiledPageActionDescriptor> LoadAsync(PageActionDescriptor actionDescriptor, EndpointMetadataCollection endpointMetadata)
|
||||
{
|
||||
if (actionDescriptor == null)
|
||||
{
|
||||
|
|
@ -79,10 +82,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
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 context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type.GetTypeInfo());
|
||||
|
|
@ -110,7 +113,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
routeNames: new HashSet<string>(StringComparer.OrdinalIgnoreCase),
|
||||
action: compiled,
|
||||
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);
|
||||
|
||||
// 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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,7 +78,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
// We found an endpoint instance that has a PageActionDescriptor, but not a
|
||||
// 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)
|
||||
{
|
||||
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