Transfer endpoint metadata from PageActionDescriptor to CompiledPageActionDescriptor (#19061)

Fixes https://github.com/dotnet/aspnetcore/issues/17300
This commit is contained in:
Pranav K 2020-02-14 10:53:47 -08:00 committed by GitHub
parent a085554993
commit e29c495166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 165 additions and 5 deletions

View File

@ -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; }

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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();
}
}
}

View File

@ -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();
});
}
}
}