Make AuthorizeFilter work in endpoint routing (#9099)
* Make AuthorizeFilter work in endpoint routing Fixes https://github.com/aspnet/AspNetCore/issues/8387
This commit is contained in:
parent
67e08728ca
commit
b93bc433db
|
|
@ -1,5 +1,4 @@
|
|||
using BasicTestApp;
|
||||
using BasicTestApp.RouterTest;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Components.Server;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
|
@ -38,7 +37,17 @@ namespace TestServer
|
|||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
AllowCorsForAnyLocalhostPort(app);
|
||||
// It's not enough just to return "Access-Control-Allow-Origin: *", because
|
||||
// browsers don't allow wildcards in conjunction with credentials. So we must
|
||||
// specify explicitly which origin we want to allow.
|
||||
app.UseCors(policy =>
|
||||
{
|
||||
policy.SetIsOriginAllowed(host => host.StartsWith("http://localhost:") || host.StartsWith("http://127.0.0.1:"))
|
||||
.AllowAnyHeader()
|
||||
.WithExposedHeaders("MyCustomHeader")
|
||||
.AllowAnyMethod()
|
||||
.AllowCredentials();
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
|
|
@ -82,29 +91,5 @@ namespace TestServer
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static void AllowCorsForAnyLocalhostPort(IApplicationBuilder app)
|
||||
{
|
||||
// It's not enough just to return "Access-Control-Allow-Origin: *", because
|
||||
// browsers don't allow wildcards in conjunction with credentials. So we must
|
||||
// specify explicitly which origin we want to allow.
|
||||
app.Use((context, next) =>
|
||||
{
|
||||
if (context.Request.Headers.TryGetValue("origin", out var incomingOriginValue))
|
||||
{
|
||||
var origin = incomingOriginValue.ToArray()[0];
|
||||
if (origin.StartsWith("http://localhost:") || origin.StartsWith("http://127.0.0.1:"))
|
||||
{
|
||||
context.Response.Headers.Add("Access-Control-Allow-Origin", origin);
|
||||
context.Response.Headers.Add("Access-Control-Allow-Credentials", "true");
|
||||
context.Response.Headers.Add("Access-Control-Allow-Methods", "HEAD,GET,PUT,POST,DELETE,OPTIONS");
|
||||
context.Response.Headers.Add("Access-Control-Allow-Headers", "Content-Type,TestHeader,another-header");
|
||||
context.Response.Headers.Add("Access-Control-Expose-Headers", "MyCustomHeader,TestHeader,another-header");
|
||||
}
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
// 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 Identity.DefaultUI.WebSite;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.FunctionalTests.IdentityUserTests
|
||||
{
|
||||
public class IdentityUserAuthorizationWithoutEndpointRoutingTests : AuthorizationTests<StartupWithoutEndpointRouting, IdentityDbContext>
|
||||
{
|
||||
public IdentityUserAuthorizationWithoutEndpointRoutingTests(ServerFactory<StartupWithoutEndpointRouting, IdentityDbContext> serverFactory)
|
||||
: base(serverFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// 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 Identity.DefaultUI.WebSite;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Identity.FunctionalTests.IdentityUserTests
|
||||
{
|
||||
public class IdentityUserLoginWithoutEndpointRoutingTests : LoginTests<StartupWithoutEndpointRouting, IdentityDbContext>
|
||||
{
|
||||
public IdentityUserLoginWithoutEndpointRoutingTests(ServerFactory<StartupWithoutEndpointRouting, IdentityDbContext> serverFactory) : base(serverFactory)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ namespace Identity.DefaultUI.WebSite
|
|||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
// This prevents running out of file watchers on some linux machines
|
||||
((PhysicalFileProvider)env.WebRootFileProvider).UseActivePolling = false;
|
||||
|
|
@ -80,14 +80,7 @@ namespace Identity.DefaultUI.WebSite
|
|||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
// This has to be disabled due to https://github.com/aspnet/AspNetCore/issues/8387
|
||||
//
|
||||
// UseAuthorization does not currently work with Razor pages, and it impacts
|
||||
// many of the tests here. Uncomment when this is fixed so that we test what is recommended
|
||||
// for users.
|
||||
//
|
||||
//app.UseAuthorization();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Identity.DefaultUI.WebSite
|
||||
{
|
||||
public class StartupWithoutEndpointRouting : StartupBase<IdentityUser, IdentityDbContext>
|
||||
{
|
||||
public StartupWithoutEndpointRouting(IConfiguration configuration) : base(configuration)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
base.ConfigureServices(services);
|
||||
services.AddMvc(options => options.EnableEndpointRouting = false);
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public override void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
// This prevents running out of file watchers on some linux machines
|
||||
((PhysicalFileProvider)env.WebRootFileProvider).UseActivePolling = false;
|
||||
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseDatabaseErrorPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseCookiePolicy();
|
||||
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,8 +145,23 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
// Get the most significant CORS metadata for the endpoint
|
||||
// For backwards compatibility reasons this is then downcast to Enable/Disable metadata
|
||||
var corsMetadata = endpoint?.Metadata.GetMetadata<ICorsMetadata>();
|
||||
|
||||
if (corsMetadata is IDisableCorsAttribute)
|
||||
{
|
||||
var isOptionsRequest = string.Equals(
|
||||
context.Request.Method,
|
||||
CorsConstants.PreflightHttpMethod,
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var isCorsPreflightRequest = isOptionsRequest && context.Request.Headers.ContainsKey(CorsConstants.AccessControlRequestMethod);
|
||||
|
||||
if (isCorsPreflightRequest)
|
||||
{
|
||||
// If this is a preflight request, and we disallow CORS, complete the request
|
||||
context.Response.StatusCode = StatusCodes.Status204NoContent;
|
||||
return;
|
||||
}
|
||||
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Http.Endpoints;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
|
@ -626,6 +625,67 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
|
|||
Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_HasEndpointWithEnableMetadata_HasSignificantDisableCors_ReturnsNoContentForPreflightRequest()
|
||||
{
|
||||
// Arrange
|
||||
var corsService = Mock.Of<ICorsService>();
|
||||
var policyProvider = Mock.Of<ICorsPolicyProvider>();
|
||||
var loggerFactory = NullLoggerFactory.Instance;
|
||||
|
||||
var middleware = new CorsMiddleware(
|
||||
c => { throw new Exception("Should not be called."); },
|
||||
corsService,
|
||||
loggerFactory,
|
||||
"DefaultPolicyName");
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(new EnableCorsAttribute(), new DisableCorsAttribute()), "Test endpoint"));
|
||||
httpContext.Request.Method = "OPTIONS";
|
||||
httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" });
|
||||
httpContext.Request.Headers.Add(CorsConstants.AccessControlRequestMethod, new[] { "GET" });
|
||||
|
||||
// Act
|
||||
await middleware.Invoke(httpContext, policyProvider);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(StatusCodes.Status204NoContent, httpContext.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_HasEndpointWithEnableMetadata_HasSignificantDisableCors_ExecutesNextMiddleware()
|
||||
{
|
||||
// Arrange
|
||||
var executed = false;
|
||||
var corsService = Mock.Of<ICorsService>();
|
||||
var policyProvider = Mock.Of<ICorsPolicyProvider>();
|
||||
var loggerFactory = NullLoggerFactory.Instance;
|
||||
|
||||
var middleware = new CorsMiddleware(
|
||||
c =>
|
||||
{
|
||||
executed = true;
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
corsService,
|
||||
loggerFactory,
|
||||
"DefaultPolicyName");
|
||||
|
||||
var httpContext = new DefaultHttpContext();
|
||||
httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(new EnableCorsAttribute(), new DisableCorsAttribute()), "Test endpoint"));
|
||||
httpContext.Request.Method = "GET";
|
||||
httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" });
|
||||
httpContext.Request.Headers.Add(CorsConstants.AccessControlRequestMethod, new[] { "GET" });
|
||||
|
||||
// Act
|
||||
await middleware.Invoke(httpContext, policyProvider);
|
||||
|
||||
// Assert
|
||||
Assert.True(executed);
|
||||
Mock.Get(policyProvider).Verify(v => v.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()), Times.Never());
|
||||
Mock.Get(corsService).Verify(v => v.EvaluatePolicy(It.IsAny<HttpContext>(), It.IsAny<CorsPolicy>()), Times.Never());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Invoke_HasEndpointWithEnableMetadata_MiddlewareHasPolicy_RunsCorsWithPolicyName()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -127,13 +127,16 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
}
|
||||
}
|
||||
|
||||
private static void AddEndpointMetadata(SelectorModel selector, IList<object> metadata)
|
||||
private static void AddEndpointMetadata(SelectorModel selector, IList<object> controllerMetadata)
|
||||
{
|
||||
if (metadata != null)
|
||||
if (controllerMetadata != null)
|
||||
{
|
||||
for (var i = 0; i < metadata.Count; i++)
|
||||
// It is criticial to get the order in which metadata appears in endpoint metadata correct. More significant metadata
|
||||
// must appear later in the sequence. In this case, the values in `controllerMetadata` should have their order
|
||||
// preserved, but appear earlier than the entries in `selector.EndpointMetadata`.
|
||||
for (var i = 0; i < controllerMetadata.Count; i++)
|
||||
{
|
||||
selector.EndpointMetadata.Add(metadata[i]);
|
||||
selector.EndpointMetadata.Insert(i, controllerMetadata[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,21 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
internal class AuthorizationApplicationModelProvider : IApplicationModelProvider
|
||||
{
|
||||
private readonly MvcOptions _mvcOptions;
|
||||
private readonly IAuthorizationPolicyProvider _policyProvider;
|
||||
|
||||
public AuthorizationApplicationModelProvider(IAuthorizationPolicyProvider policyProvider)
|
||||
public AuthorizationApplicationModelProvider(
|
||||
IAuthorizationPolicyProvider policyProvider,
|
||||
IOptions<MvcOptions> mvcOptions)
|
||||
{
|
||||
_policyProvider = policyProvider;
|
||||
_mvcOptions = mvcOptions.Value;
|
||||
}
|
||||
|
||||
public int Order => -1000 + 10;
|
||||
|
|
@ -32,6 +37,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (_mvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
// When using endpoint routing, the AuthorizationMiddleware does the work that Auth filters would otherwise perform.
|
||||
// Consequently we do not need to convert authorization attributes to filters.
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var controllerModel in context.Result.Controllers)
|
||||
{
|
||||
var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray();
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
|
||||
public IList<IActionConstraintMetadata> ActionConstraints { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="EndpointMetadata"/> associated with the <see cref="SelectorModel"/>.
|
||||
/// </summary>
|
||||
public IList<object> EndpointMetadata { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization.Policy;
|
||||
using Microsoft.AspNetCore.Http.Endpoints;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Core;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
|
@ -22,9 +23,6 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
/// </summary>
|
||||
public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory
|
||||
{
|
||||
// Property key set by authorization middleware when it is run
|
||||
private const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareInvoked";
|
||||
|
||||
private AuthorizationPolicy _effectivePolicy;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -116,6 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
{
|
||||
return Task.FromResult(Policy);
|
||||
}
|
||||
|
||||
if (PolicyProvider == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
|
|
@ -127,7 +126,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
return AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData);
|
||||
}
|
||||
|
||||
private async Task<AuthorizationPolicy> GetEffectivePolicyAsync(AuthorizationFilterContext context)
|
||||
internal async Task<AuthorizationPolicy> GetEffectivePolicyAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
if (_effectivePolicy != null)
|
||||
{
|
||||
|
|
@ -154,6 +153,27 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
}
|
||||
}
|
||||
|
||||
var endpoint = context.HttpContext.GetEndpoint();
|
||||
if (endpoint != null)
|
||||
{
|
||||
// When doing endpoint routing, MVC does not create filters for any authorization specific metadata i.e [Authorize] does not
|
||||
// get translated into AuthorizeFilter. Consequently, there are some rough edges when an application uses a mix of AuthorizeFilter
|
||||
// explicilty configured by the user (e.g. global auth filter), and uses endpoint metadata.
|
||||
// To keep the behavior of AuthFilter identical to pre-endpoint routing, we will gather auth data from endpoint metadata
|
||||
// and produce a policy using this. This would mean we would have effectively run some auth twice, but it maintains compat.
|
||||
var policyProvider = PolicyProvider ?? context.HttpContext.RequestServices.GetRequiredService<IAuthorizationPolicyProvider>();
|
||||
var endpointAuthorizeData = endpoint.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
|
||||
|
||||
var endpointPolicy = await AuthorizationPolicy.CombineAsync(policyProvider, endpointAuthorizeData);
|
||||
if (endpointPolicy != null)
|
||||
{
|
||||
builder.Combine(endpointPolicy);
|
||||
}
|
||||
|
||||
// We cannot cache the policy since it varies by endpoint metadata.
|
||||
canCache = false;
|
||||
}
|
||||
|
||||
effectivePolicy = builder?.Build() ?? effectivePolicy;
|
||||
|
||||
// We can cache the effective policy when there is no custom policy provider
|
||||
|
|
@ -173,12 +193,6 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (context.HttpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
|
||||
{
|
||||
// Authorization has already run in middleware. Don't re-run for performance
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.IsEffectivePolicy(this))
|
||||
{
|
||||
return;
|
||||
|
|
@ -196,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);
|
||||
|
||||
// Allow Anonymous skips all authorization
|
||||
if (HasAllowAnonymous(context.Filters))
|
||||
if (HasAllowAnonymous(context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -226,8 +240,9 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
return AuthorizationApplicationModelProvider.GetFilter(policyProvider, AuthorizeData);
|
||||
}
|
||||
|
||||
private static bool HasAllowAnonymous(IList<IFilterMetadata> filters)
|
||||
private static bool HasAllowAnonymous(AuthorizationFilterContext context)
|
||||
{
|
||||
var filters = context.Filters;
|
||||
for (var i = 0; i < filters.Count; i++)
|
||||
{
|
||||
if (filters[i] is IAllowAnonymousFilter)
|
||||
|
|
@ -236,6 +251,15 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
}
|
||||
}
|
||||
|
||||
// When doing endpoint routing, MVC does not add AllowAnonymousFilters for AllowAnonymousAttributes that
|
||||
// were discovered on controllers and actions. To maintain compat with 2.x,
|
||||
// we'll check for the presence of IAllowAnonymous in endpoint metadata.
|
||||
var endpoint = context.HttpContext.GetEndpoint();
|
||||
if (endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,10 +67,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
{
|
||||
// This one is OK, we implement this in endpoint routing.
|
||||
}
|
||||
else if (actionConstraint.GetType().FullName == "Microsoft.AspNetCore.Mvc.Cors.CorsHttpMethodActionConstraint")
|
||||
{
|
||||
// This one is OK, we implement this in endpoint routing.
|
||||
}
|
||||
else if (actionConstraint.GetType() == typeof(ConsumesAttribute))
|
||||
{
|
||||
// This one is OK, we implement this in endpoint routing.
|
||||
|
|
|
|||
|
|
@ -17,11 +17,51 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
public class AuthorizationApplicationModelProviderTest
|
||||
{
|
||||
private readonly IOptions<MvcOptions> OptionsWithoutEndpointRouting = Options.Create(new MvcOptions { EnableEndpointRouting = false });
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AuthorizeAttribute_DoesNothing_WhenEnableRoutingIsEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new AuthorizationApplicationModelProvider(
|
||||
new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())),
|
||||
Options.Create(new MvcOptions()));
|
||||
var controllerType = typeof(AccountController);
|
||||
var context = CreateProviderContext(controllerType);
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
var controller = Assert.Single(context.Result.Controllers);
|
||||
Assert.Empty(controller.Filters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AllowAnonymousAttribute_DoesNothing_WhenEnableRoutingIsEnabled()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new AuthorizationApplicationModelProvider(
|
||||
new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())),
|
||||
Options.Create(new MvcOptions()));
|
||||
var controllerType = typeof(AnonymousController);
|
||||
var context = CreateProviderContext(controllerType);
|
||||
|
||||
// Act
|
||||
provider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
var controller = Assert.Single(context.Result.Controllers);
|
||||
Assert.Empty(controller.Filters);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_AuthorizeAttributeAddsAuthorizeFilter()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new AuthorizationApplicationModelProvider(new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())));
|
||||
var provider = new AuthorizationApplicationModelProvider(
|
||||
new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())),
|
||||
OptionsWithoutEndpointRouting);
|
||||
var controllerType = typeof(AccountController);
|
||||
var context = CreateProviderContext(controllerType);
|
||||
|
||||
|
|
@ -41,7 +81,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
options.Value.AddPolicy("Base", policy => policy.RequireClaim("Basic").RequireClaim("Basic2"));
|
||||
options.Value.AddPolicy("Derived", policy => policy.RequireClaim("Derived"));
|
||||
|
||||
var provider = new AuthorizationApplicationModelProvider(new DefaultAuthorizationPolicyProvider(options));
|
||||
var provider = new AuthorizationApplicationModelProvider(
|
||||
new DefaultAuthorizationPolicyProvider(options),
|
||||
OptionsWithoutEndpointRouting);
|
||||
var context = CreateProviderContext(typeof(DerivedController));
|
||||
|
||||
// Act
|
||||
|
|
@ -65,7 +107,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
public void CreateControllerModelAndActionModel_AllowAnonymousAttributeAddsAllowAnonymousFilter()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new AuthorizationApplicationModelProvider(new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())));
|
||||
var provider = new AuthorizationApplicationModelProvider(
|
||||
new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())),
|
||||
OptionsWithoutEndpointRouting);
|
||||
var context = CreateProviderContext(typeof(AnonymousController));
|
||||
|
||||
// Act
|
||||
|
|
@ -91,7 +135,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
authOptions.Value.AddPolicy("Base", authorizationPolicy);
|
||||
var policyProvider = new DefaultAuthorizationPolicyProvider(authOptions);
|
||||
|
||||
var provider = new AuthorizationApplicationModelProvider(policyProvider);
|
||||
var provider = new AuthorizationApplicationModelProvider(policyProvider, OptionsWithoutEndpointRouting);
|
||||
var context = CreateProviderContext(typeof(BaseController));
|
||||
|
||||
// Act
|
||||
|
|
@ -119,7 +163,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
.Returns(Task.FromResult(authorizationPolicy))
|
||||
.Verifiable();
|
||||
|
||||
var provider = new AuthorizationApplicationModelProvider(authorizationPolicyProviderMock.Object);
|
||||
var provider = new AuthorizationApplicationModelProvider(authorizationPolicyProviderMock.Object, OptionsWithoutEndpointRouting);
|
||||
|
||||
// Act
|
||||
var action = GetBaseControllerActionModel(provider);
|
||||
|
|
@ -136,9 +180,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
// Arrange
|
||||
var provider = new AuthorizationApplicationModelProvider(
|
||||
new DefaultAuthorizationPolicyProvider(
|
||||
Options.Create(new AuthorizationOptions())
|
||||
));
|
||||
new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())),
|
||||
OptionsWithoutEndpointRouting);
|
||||
var context = CreateProviderContext(typeof(NoAuthController));
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -266,16 +266,16 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
Assert.NotNull(anonymousAction.EndpointMetadata);
|
||||
|
||||
Assert.Collection(anonymousAction.EndpointMetadata,
|
||||
metadata => Assert.IsType<AllowAnonymousAttribute>(metadata),
|
||||
metadata => Assert.IsType<AuthorizeAttribute>(metadata));
|
||||
metadata => Assert.IsType<AuthorizeAttribute>(metadata),
|
||||
metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
|
||||
|
||||
var authorizeAction = Assert.Single(descriptors, a => a.RouteValues["action"] == "AuthorizeAction");
|
||||
|
||||
Assert.NotNull(authorizeAction.EndpointMetadata);
|
||||
|
||||
Assert.Collection(authorizeAction.EndpointMetadata,
|
||||
metadata => Assert.Equal("ActionPolicy", Assert.IsType<AuthorizeAttribute>(metadata).Policy),
|
||||
metadata => Assert.Equal("ControllerPolicy", Assert.IsType<AuthorizeAttribute>(metadata).Policy));
|
||||
metadata => Assert.Equal("ControllerPolicy", Assert.IsType<AuthorizeAttribute>(metadata).Policy),
|
||||
metadata => Assert.Equal("ActionPolicy", Assert.IsType<AuthorizeAttribute>(metadata).Policy));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -291,6 +291,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
Assert.NotNull(action.EndpointMetadata);
|
||||
|
||||
Assert.Collection(action.EndpointMetadata,
|
||||
metadata => Assert.IsType<RouteAttribute>(metadata),
|
||||
metadata => Assert.IsType<HttpGetAttribute>(metadata),
|
||||
metadata =>
|
||||
{
|
||||
|
|
@ -298,8 +299,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
|
||||
Assert.False(httpMethodMetadata.AcceptCorsPreflight);
|
||||
Assert.Equal("GET", Assert.Single(httpMethodMetadata.HttpMethods));
|
||||
},
|
||||
metadata => Assert.IsType<RouteAttribute>(metadata));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -8,11 +8,15 @@ using System.Security.Claims;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Http.Endpoints;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -20,6 +24,8 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
{
|
||||
public class AuthorizeFilterTest
|
||||
{
|
||||
private readonly ActionContext ActionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor());
|
||||
|
||||
[Fact]
|
||||
public void InvalidUser()
|
||||
{
|
||||
|
|
@ -49,26 +55,6 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
Assert.IsType<ChallengeResult>(authorizationContext.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OnAuthorizationAsync_AuthorizationMiddlewareHasRun_NoOp()
|
||||
{
|
||||
// Arrange
|
||||
var authorizationContext = GetAuthorizationContext(anonymous: true);
|
||||
authorizationContext.HttpContext.Items["__AuthorizationMiddlewareInvoked"] = new object();
|
||||
|
||||
var authorizeFilterFactory = new AuthorizeFilter();
|
||||
var filterFactory = authorizeFilterFactory as IFilterFactory;
|
||||
var authorizeFilter = (AuthorizeFilter)filterFactory.CreateInstance(
|
||||
authorizationContext.HttpContext.RequestServices);
|
||||
authorizationContext.Filters.Add(authorizeFilter);
|
||||
|
||||
// Act
|
||||
await authorizeFilter.OnAuthorizationAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
Assert.Null(authorizationContext.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuthorizeFilter_CreatedWithAuthorizeData_ThrowsWhenOnAuthorizationAsyncIsCalled()
|
||||
{
|
||||
|
|
@ -527,6 +513,87 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
Assert.Same(policyProvider, actual.PolicyProvider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEffectivePolicyAsync_ReturnsCurrentPolicy_WhenNoEndpointMetadataIsAvailable()
|
||||
{
|
||||
// Arrange
|
||||
var policy = new AuthorizationPolicyBuilder()
|
||||
.RequireAssertion(_ => true)
|
||||
.Build();
|
||||
var filter = new AuthorizeFilter(policy);
|
||||
|
||||
var context = new AuthorizationFilterContext(ActionContext, new[] { filter });
|
||||
|
||||
// Act
|
||||
var effectivePolicy = await filter.GetEffectivePolicyAsync(context);
|
||||
|
||||
// Assert
|
||||
//
|
||||
// Verify the policy is cached
|
||||
Assert.Same(effectivePolicy, await filter.GetEffectivePolicyAsync(context));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEffectivePolicyAsync_CombinesPoliciesFromAuthFilters()
|
||||
{
|
||||
// Arrange
|
||||
var policy1 = new AuthorizationPolicyBuilder()
|
||||
.RequireClaim("Claim1")
|
||||
.Build();
|
||||
|
||||
var policy2 = new AuthorizationPolicyBuilder()
|
||||
.RequireClaim("Claim2")
|
||||
.Build();
|
||||
var filter1 = new AuthorizeFilter(policy1);
|
||||
var filter2 = new AuthorizeFilter(policy2);
|
||||
|
||||
var context = new AuthorizationFilterContext(ActionContext, new[] { filter1, filter2 });
|
||||
|
||||
// Act
|
||||
var effectivePolicy = await filter1.GetEffectivePolicyAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(policy1, effectivePolicy);
|
||||
Assert.NotSame(policy2, effectivePolicy);
|
||||
Assert.Equal(new[] { "Claim1", "Claim2" }, effectivePolicy.Requirements.Cast<ClaimsAuthorizationRequirement>().Select(c => c.ClaimType));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetEffectivePolicyAsync_CombinesPoliciesFromEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var policy1 = new AuthorizationPolicyBuilder()
|
||||
.RequireClaim("Claim1")
|
||||
.Build();
|
||||
|
||||
var policy2 = new AuthorizationPolicyBuilder()
|
||||
.RequireClaim("Claim2")
|
||||
.Build();
|
||||
|
||||
var filter = new AuthorizeFilter(policy1);
|
||||
var options = new AuthorizationOptions();
|
||||
options.AddPolicy("policy2", policy2);
|
||||
var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(options));
|
||||
|
||||
ActionContext.HttpContext.RequestServices = new ServiceCollection()
|
||||
.AddSingleton<IAuthorizationPolicyProvider>(policyProvider)
|
||||
.BuildServiceProvider();
|
||||
|
||||
ActionContext.HttpContext.SetEndpoint(new Endpoint(
|
||||
_ => null,
|
||||
new EndpointMetadataCollection(new AuthorizeAttribute("policy2")),
|
||||
"test"));
|
||||
var context = new AuthorizationFilterContext(ActionContext, new[] { filter, });
|
||||
|
||||
// Act
|
||||
var effectivePolicy = await filter.GetEffectivePolicyAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotSame(policy1, effectivePolicy);
|
||||
Assert.NotSame(policy2, effectivePolicy);
|
||||
Assert.Equal(new[] { "Claim1", "Claim2" }, effectivePolicy.Requirements.Cast<ClaimsAuthorizationRequirement>().Select(c => c.ClaimType));
|
||||
}
|
||||
|
||||
private AuthorizationFilterContext GetAuthorizationContext(
|
||||
bool anonymous = false,
|
||||
Action<IServiceCollection> registerServices = null)
|
||||
|
|
@ -580,6 +647,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization
|
|||
httpContext.SetupGet(c => c.RequestServices).Returns(serviceProvider);
|
||||
var contextItems = new Dictionary<object, object>();
|
||||
httpContext.SetupGet(c => c.Items).Returns(contextItems);
|
||||
httpContext.SetupGet(c => c.Features).Returns(Mock.Of<IFeatureCollection>());
|
||||
|
||||
// AuthorizationFilterContext
|
||||
var actionContext = new ActionContext(
|
||||
|
|
|
|||
|
|
@ -7,11 +7,19 @@ using Microsoft.AspNetCore.Cors.Infrastructure;
|
|||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Cors
|
||||
{
|
||||
internal class CorsApplicationModelProvider : IApplicationModelProvider
|
||||
{
|
||||
private readonly MvcOptions _mvcOptions;
|
||||
|
||||
public CorsApplicationModelProvider(IOptions<MvcOptions> mvcOptions)
|
||||
{
|
||||
_mvcOptions = mvcOptions.Value;
|
||||
}
|
||||
|
||||
public int Order => -1000 + 10;
|
||||
|
||||
public void OnProvidersExecuted(ApplicationModelProviderContext context)
|
||||
|
|
@ -20,6 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
// Intentionally empty.
|
||||
}
|
||||
|
||||
|
|
@ -30,8 +39,21 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (_mvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
// When doing endpoint routing, translate IEnableCorsAttribute to an HttpMethodMetadata with CORS enabled.
|
||||
ConfigureCorsEndpointMetadata(context.Result);
|
||||
}
|
||||
else
|
||||
{
|
||||
ConfigureCorsFilters(context);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureCorsFilters(ApplicationModelProviderContext context)
|
||||
{
|
||||
var isCorsEnabledGlobally = context.Result.Filters.OfType<ICorsAuthorizationFilter>().Any() ||
|
||||
context.Result.Filters.OfType<CorsAuthorizationFilterFactory>().Any();
|
||||
context.Result.Filters.OfType<CorsAuthorizationFilterFactory>().Any();
|
||||
|
||||
foreach (var controllerModel in context.Result.Controllers)
|
||||
{
|
||||
|
|
@ -67,13 +89,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
|
||||
if (isCorsEnabledGlobally || corsOnController || corsOnAction)
|
||||
{
|
||||
UpdateActionToAcceptCorsPreflight(actionModel);
|
||||
ConfigureCorsActionConstraint(actionModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateActionToAcceptCorsPreflight(ActionModel actionModel)
|
||||
private static void ConfigureCorsActionConstraint(ActionModel actionModel)
|
||||
{
|
||||
for (var i = 0; i < actionModel.Selectors.Count; i++)
|
||||
{
|
||||
|
|
@ -86,12 +108,36 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
selectorModel.ActionConstraints[j] = new CorsHttpMethodActionConstraint(httpConstraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < selectorModel.EndpointMetadata.Count; j++)
|
||||
private static void ConfigureCorsEndpointMetadata(ApplicationModel applicationModel)
|
||||
{
|
||||
foreach (var controller in applicationModel.Controllers)
|
||||
{
|
||||
var corsOnController = controller.Attributes.OfType<IDisableCorsAttribute>().Any() ||
|
||||
controller.Attributes.OfType<IEnableCorsAttribute>().Any();
|
||||
|
||||
foreach (var action in controller.Actions)
|
||||
{
|
||||
if (selectorModel.EndpointMetadata[j] is HttpMethodMetadata httpMethodMetadata)
|
||||
var corsOnAction = action.Attributes.OfType<IDisableCorsAttribute>().Any() ||
|
||||
action.Attributes.OfType<IEnableCorsAttribute>().Any();
|
||||
|
||||
if (!corsOnController && !corsOnAction)
|
||||
{
|
||||
selectorModel.EndpointMetadata[j] = new HttpMethodMetadata(httpMethodMetadata.HttpMethods, true);
|
||||
// No CORS here.
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var selector in action.Selectors)
|
||||
{
|
||||
for (var i = 0; i < selector.EndpointMetadata.Count; i++)
|
||||
{
|
||||
if (selector.EndpointMetadata[i] is HttpMethodMetadata httpMethodMetadata)
|
||||
{
|
||||
selector.EndpointMetadata[i] = new HttpMethodMetadata(httpMethodMetadata.HttpMethods, acceptCorsPreflight: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,8 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
|
||||
{
|
||||
// If this was a preflight, there is no need to run anything else.
|
||||
// Also the response is always 200 so that anyone after mvc can handle the pre flight request.
|
||||
context.Result = new StatusCodeResult(StatusCodes.Status200OK);
|
||||
context.Result = new StatusCodeResult(StatusCodes.Status204NoContent);
|
||||
}
|
||||
|
||||
// Continue with other filters and action.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Microsoft.Extensions.Primitives;
|
|||
|
||||
namespace Microsoft.AspNetCore.Mvc.Cors
|
||||
{
|
||||
// Don't casually change the name of this. We reference the full type name in ActionConstraintCache.
|
||||
internal class CorsHttpMethodActionConstraint : HttpMethodActionConstraint
|
||||
{
|
||||
private readonly string OriginHeader = "Origin";
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
|
||||
{
|
||||
// Short circuit if the request is preflight as that should not result in action execution.
|
||||
context.Result = new StatusCodeResult(StatusCodes.Status200OK);
|
||||
context.Result = new StatusCodeResult(StatusCodes.Status204NoContent);
|
||||
}
|
||||
|
||||
// Let the action be executed.
|
||||
|
|
|
|||
|
|
@ -21,11 +21,59 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
{
|
||||
public class CorsApplicationModelProviderTest
|
||||
{
|
||||
private readonly IOptions<MvcOptions> OptionsWithoutEndpointRouting = Options.Create(new MvcOptions { EnableEndpointRouting = false });
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_EnableCorsAttributeAddsCorsAuthorizationFilterFactory()
|
||||
public void OnProvidersExecuting_SetsEndpointMetadata_IfCorsAttributeIsPresentOnController()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(Options.Create(new MvcOptions()));
|
||||
var context = GetProviderContext(typeof(CorsController));
|
||||
|
||||
// Act
|
||||
corsProvider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
var model = Assert.Single(context.Result.Controllers);
|
||||
Assert.Empty(model.Filters);
|
||||
|
||||
var action = Assert.Single(model.Actions);
|
||||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsNotType<CorsHttpMethodActionConstraint>(constraint);
|
||||
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_SetsEndpointMetadata_IfCorsAttributeIsPresentOnAction()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider(Options.Create(new MvcOptions()));
|
||||
var context = GetProviderContext(typeof(DisableCorsActionController));
|
||||
|
||||
// Act
|
||||
corsProvider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
var model = Assert.Single(context.Result.Controllers);
|
||||
Assert.Empty(model.Filters);
|
||||
|
||||
var action = Assert.Single(model.Actions);
|
||||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsNotType<CorsHttpMethodActionConstraint>(constraint);
|
||||
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_WithoutGlobalAuthorizationFilter_EnableCorsAttributeAddsCorsAuthorizationFilterFactory()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(CorsController));
|
||||
|
||||
// Act
|
||||
|
|
@ -38,15 +86,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_DisableCorsAttributeAddsDisableCorsAuthorizationFilter()
|
||||
public void OnProvidersExecuting_WithoutGlobalAuthorizationFilter_DisableCorsAttributeAddsDisableCorsAuthorizationFilter()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(DisableCorsController));
|
||||
|
||||
// Act
|
||||
|
|
@ -59,15 +105,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_CustomCorsFilter_EnablesCorsPreflight()
|
||||
public void OnProvidersExecuting_WithoutGlobalAuthorizationFilter_CustomCorsFilter_EnablesCorsPreflight()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(CustomCorsFilterController));
|
||||
|
||||
// Act
|
||||
|
|
@ -79,15 +123,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildActionModel_EnableCorsAttributeAddsCorsAuthorizationFilterFactory()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(EnableCorsController));
|
||||
|
||||
// Act
|
||||
|
|
@ -100,15 +142,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildActionModel_DisableCorsAttributeAddsDisableCorsAuthorizationFilter()
|
||||
public void BuildActionModel_WithoutGlobalAuthorizationFilter_DisableCorsAttributeAddsDisableCorsAuthorizationFilter()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(DisableCorsActionController));
|
||||
|
||||
// Act
|
||||
|
|
@ -121,15 +161,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildActionModel_CustomCorsAuthorizationFilterOnAction_EnablesCorsPreflight()
|
||||
public void BuildActionModel_WithoutGlobalAuthorizationFilter_CustomCorsAuthorizationFilterOnAction_EnablesCorsPreflight()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(CustomCorsFilterOnActionController));
|
||||
|
||||
// Act
|
||||
|
|
@ -141,15 +179,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_EnableCorsGloballyEnablesCorsPreflight()
|
||||
public void OnProvidersExecuting_WithoutGlobalAuthorizationFilter_EnableCorsGloballyEnablesCorsPreflight()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(RegularController));
|
||||
|
||||
context.Result.Filters.Add(
|
||||
|
|
@ -164,15 +200,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_DisableCorsGloballyEnablesCorsPreflight()
|
||||
public void OnProvidersExecuting_WithoutGlobalAuthorizationFilter_DisableCorsGloballyEnablesCorsPreflight()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(RegularController));
|
||||
context.Result.Filters.Add(new DisableCorsAuthorizationFilter());
|
||||
|
||||
|
|
@ -185,15 +219,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_CustomCorsFilterGloballyEnablesCorsPreflight()
|
||||
public void OnProvidersExecuting_WithoutGlobalAuthorizationFilter_CustomCorsFilterGloballyEnablesCorsPreflight()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(RegularController));
|
||||
context.Result.Filters.Add(new CustomCorsFilterAttribute());
|
||||
|
||||
|
|
@ -206,15 +238,13 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.True(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateControllerModel_CorsNotInUseDoesNotOverrideHttpConstraints()
|
||||
public void OnProvidersExecuting_WithoutGlobalAuthorizationFilter_CorsNotInUseDoesNotOverrideHttpConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var corsProvider = new CorsApplicationModelProvider();
|
||||
var corsProvider = new CorsApplicationModelProvider(OptionsWithoutEndpointRouting);
|
||||
var context = GetProviderContext(typeof(RegularController));
|
||||
|
||||
// Act
|
||||
|
|
@ -226,8 +256,6 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
var selector = Assert.Single(action.Selectors);
|
||||
var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint);
|
||||
Assert.IsNotType<CorsHttpMethodActionConstraint>(constraint);
|
||||
var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType<HttpMethodMetadata>());
|
||||
Assert.False(httpMethodMetadata.AcceptCorsPreflight);
|
||||
}
|
||||
|
||||
private static ApplicationModelProviderContext GetProviderContext(Type controllerType)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
|
||||
// Assert
|
||||
var response = authorizationContext.HttpContext.Response;
|
||||
Assert.Equal(200, response.StatusCode);
|
||||
Assert.Equal(204, response.StatusCode);
|
||||
Assert.Equal("http://example.com", response.Headers[CorsConstants.AccessControlAllowOrigin]);
|
||||
Assert.Equal("header1,header2", response.Headers[CorsConstants.AccessControlAllowHeaders]);
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PreFlight_FailedMatch_Writes200()
|
||||
public async Task PreFlight_FailedMatch_RespondsWith204NoContent()
|
||||
{
|
||||
// Arrange
|
||||
var mockEngine = GetFailingEngine();
|
||||
|
|
@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
await authorizationContext.Result.ExecuteResultAsync(authorizationContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(200, authorizationContext.HttpContext.Response.StatusCode);
|
||||
Assert.Equal(204, authorizationContext.HttpContext.Response.StatusCode);
|
||||
Assert.Empty(authorizationContext.HttpContext.Response.Headers);
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
|
||||
// Assert
|
||||
var response = authorizationContext.HttpContext.Response;
|
||||
Assert.Equal(200, response.StatusCode);
|
||||
Assert.Equal(204, response.StatusCode);
|
||||
Assert.Equal("http://example.com", response.Headers[CorsConstants.AccessControlAllowOrigin]);
|
||||
Assert.Equal("exposed1,exposed2", response.Headers[CorsConstants.AccessControlExposeHeaders]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors
|
|||
|
||||
// Assert
|
||||
var statusCodeResult = Assert.IsType<StatusCodeResult>(authorizationFilterContext.Result);
|
||||
Assert.Equal(StatusCodes.Status200OK, statusCodeResult.StatusCode);
|
||||
Assert.Equal(StatusCodes.Status204NoContent, statusCodeResult.StatusCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
public Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor ActionDescriptor { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public string AreaName { get { throw null; } }
|
||||
public System.Reflection.TypeInfo DeclaredModelType { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public System.Collections.Generic.IList<object> EndpointMetadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public System.Collections.Generic.IList<Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata> Filters { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public System.Collections.Generic.IList<Microsoft.AspNetCore.Mvc.ApplicationModels.PageHandlerModel> HandlerMethods { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
public System.Collections.Generic.IList<Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel> HandlerProperties { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
|
||||
|
|
|
|||
|
|
@ -5,16 +5,21 @@ using System;
|
|||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
internal class AuthorizationPageApplicationModelProvider : IPageApplicationModelProvider
|
||||
{
|
||||
private readonly IAuthorizationPolicyProvider _policyProvider;
|
||||
private readonly MvcOptions _mvcOptions;
|
||||
|
||||
public AuthorizationPageApplicationModelProvider(IAuthorizationPolicyProvider policyProvider)
|
||||
public AuthorizationPageApplicationModelProvider(
|
||||
IAuthorizationPolicyProvider policyProvider,
|
||||
IOptions<MvcOptions> mvcOptions)
|
||||
{
|
||||
_policyProvider = policyProvider;
|
||||
_mvcOptions = mvcOptions.Value;
|
||||
}
|
||||
|
||||
// The order is set to execute after the DefaultPageApplicationModelProvider.
|
||||
|
|
@ -27,6 +32,13 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if (_mvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
// When using endpoint routing, the AuthorizationMiddleware does the work that Auth filters would otherwise perform.
|
||||
// Consequently we do not need to convert authorization attributes to filters.
|
||||
return;
|
||||
}
|
||||
|
||||
var pageModel = context.PageApplicationModel;
|
||||
var authorizeData = pageModel.HandlerTypeAttributes.OfType<IAuthorizeData>().ToArray();
|
||||
if (authorizeData.Length > 0)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
ActionConstraints = actionDescriptor.ActionConstraints,
|
||||
AttributeRouteInfo = actionDescriptor.AttributeRouteInfo,
|
||||
BoundProperties = boundProperties,
|
||||
EndpointMetadata = actionDescriptor.EndpointMetadata,
|
||||
EndpointMetadata = CreateEndPointMetadata(applicationModel),
|
||||
FilterDescriptors = filters,
|
||||
HandlerMethods = handlerMethods,
|
||||
HandlerTypeInfo = applicationModel.HandlerType,
|
||||
|
|
@ -61,6 +61,18 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
};
|
||||
}
|
||||
|
||||
private static IList<object> CreateEndPointMetadata(PageApplicationModel applicationModel)
|
||||
{
|
||||
var handlerMetatdata = applicationModel.HandlerTypeAttributes;
|
||||
var endpointMetadata = applicationModel.EndpointMetadata;
|
||||
|
||||
// It is criticial to get the order in which metadata appears in endpoint metadata correct. More significant metadata
|
||||
// must appear later in the sequence.
|
||||
// In this case, handlerMetadata is attributes on the Page \ PageModel, and endPointMetadata is configured via conventions. and
|
||||
// We consider the latter to be more significant.
|
||||
return Enumerable.Concat(handlerMetatdata, endpointMetadata).ToList();
|
||||
}
|
||||
|
||||
// Internal for unit testing
|
||||
internal static HandlerMethodDescriptor[] CreateHandlerMethods(PageApplicationModel applicationModel)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
HandlerMethods = new List<PageHandlerModel>();
|
||||
HandlerProperties = new List<PagePropertyModel>();
|
||||
HandlerTypeAttributes = handlerAttributes;
|
||||
EndpointMetadata = new List<object>(ActionDescriptor.EndpointMetadata ?? Array.Empty<object>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -71,6 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
HandlerMethods = new List<PageHandlerModel>(other.HandlerMethods.Select(m => new PageHandlerModel(m)));
|
||||
HandlerProperties = new List<PagePropertyModel>(other.HandlerProperties.Select(p => new PagePropertyModel(p)));
|
||||
HandlerTypeAttributes = other.HandlerTypeAttributes;
|
||||
EndpointMetadata = new List<object>(other.EndpointMetadata);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -154,5 +156,10 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
/// Gets the sequence of <see cref="PagePropertyModel"/> instances on <see cref="PageHandlerModel"/>.
|
||||
/// </summary>
|
||||
public IList<PagePropertyModel> HandlerProperties { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the endpoint metadata for this action.
|
||||
/// </summary>
|
||||
public IList<object> EndpointMetadata { get; }
|
||||
}
|
||||
}
|
||||
|
|
@ -5,15 +5,21 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
{
|
||||
public class PageConventionCollection : Collection<IPageConvention>
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private MvcOptions _mvcOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PageConventionCollection"/> class that is empty.
|
||||
/// </summary>
|
||||
public PageConventionCollection()
|
||||
: this((IServiceProvider)null)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -27,6 +33,21 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
}
|
||||
|
||||
internal PageConventionCollection(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
internal MvcOptions MvcOptions
|
||||
{
|
||||
get
|
||||
{
|
||||
// Avoid eagerly getting to the MvcOptions from the options setup for RazorPagesOptions.
|
||||
_mvcOptions ??= _serviceProvider.GetRequiredService<IOptions<MvcOptions>>().Value;
|
||||
return _mvcOptions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and adds an <see cref="IPageApplicationModelConvention"/> that invokes an action on the
|
||||
/// <see cref="PageApplicationModel"/> for the page with the specified name.
|
||||
|
|
|
|||
|
|
@ -83,6 +83,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<RazorViewEngineOptions>, RazorPagesRazorViewEngineOptionsSetup>());
|
||||
|
||||
services.TryAddEnumerable(
|
||||
ServiceDescriptor.Transient<IConfigureOptions<RazorPagesOptions>, RazorPagesOptionsSetup>());
|
||||
|
||||
// Routing
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, PageLoaderMatcherPolicy>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<MatcherPolicy, DynamicPageEndpointMatcherPolicy>());
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
|
@ -60,7 +61,31 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AllowAnonymousFilter"/> to the page with the specified name.
|
||||
/// Adds the specified <paramref name="convention"/> to <paramref name="conventions"/>.
|
||||
/// The added convention will apply to all handler properties and parameters on handler methods.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="convention">The <see cref="IParameterModelBaseConvention"/> to apply.</param>
|
||||
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
|
||||
public static PageConventionCollection Add(this PageConventionCollection conventions, IParameterModelBaseConvention convention)
|
||||
{
|
||||
if (conventions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(conventions));
|
||||
}
|
||||
|
||||
if (convention == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(convention));
|
||||
}
|
||||
|
||||
var adapter = new ParameterModelBaseConventionAdapter(convention);
|
||||
conventions.Add(adapter);
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allows anonymous access to the page with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="pageName">The page name.</param>
|
||||
|
|
@ -77,13 +102,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
|
||||
}
|
||||
|
||||
var anonymousFilter = new AllowAnonymousFilter();
|
||||
conventions.AddPageApplicationModelConvention(pageName, model => model.Filters.Add(anonymousFilter));
|
||||
conventions.AddPageApplicationModelConvention(pageName, model =>
|
||||
{
|
||||
if (conventions.MvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
model.EndpointMetadata.Add(new AllowAnonymousAttribute());
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Filters.Add(new AllowAnonymousFilter());
|
||||
}
|
||||
});
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AllowAnonymousFilter"/> to the page with the specified name located in the specified area.
|
||||
/// Allows anonymous access to the page with the specified name located in the specified area.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="areaName">The area name.</param>
|
||||
|
|
@ -115,13 +149,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
|
||||
}
|
||||
|
||||
var anonymousFilter = new AllowAnonymousFilter();
|
||||
conventions.AddAreaPageApplicationModelConvention(areaName, pageName, model => model.Filters.Add(anonymousFilter));
|
||||
conventions.AddAreaPageApplicationModelConvention(areaName, pageName, model =>
|
||||
{
|
||||
if (conventions.MvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
model.EndpointMetadata.Add(new AllowAnonymousAttribute());
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Filters.Add(new AllowAnonymousFilter());
|
||||
}
|
||||
});
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified folder.
|
||||
/// Allows anonymous access to all pages under the specified folder.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="folderPath">The folder path.</param>
|
||||
|
|
@ -138,37 +181,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath));
|
||||
}
|
||||
|
||||
var anonymousFilter = new AllowAnonymousFilter();
|
||||
conventions.AddFolderApplicationModelConvention(folderPath, model => model.Filters.Add(anonymousFilter));
|
||||
conventions.AddFolderApplicationModelConvention(folderPath, model =>
|
||||
{
|
||||
if (conventions.MvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
model.EndpointMetadata.Add(new AllowAnonymousAttribute());
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Filters.Add(new AllowAnonymousFilter());
|
||||
}
|
||||
});
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the specified <paramref name="convention"/> to <paramref name="conventions"/>.
|
||||
/// The added convention will apply to all handler properties and parameters on handler methods.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="convention">The <see cref="IParameterModelBaseConvention"/> to apply.</param>
|
||||
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
|
||||
public static PageConventionCollection Add(this PageConventionCollection conventions, IParameterModelBaseConvention convention)
|
||||
{
|
||||
if (conventions == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(conventions));
|
||||
}
|
||||
|
||||
if (convention == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(convention));
|
||||
}
|
||||
|
||||
var adapter = new ParameterModelBaseConventionAdapter(convention);
|
||||
conventions.Add(adapter);
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified area folder.
|
||||
/// Allows anonymous access to all pages under the specified area folder.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="areaName">The area name.</param>
|
||||
|
|
@ -200,13 +228,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath));
|
||||
}
|
||||
|
||||
var anonymousFilter = new AllowAnonymousFilter();
|
||||
conventions.AddAreaFolderApplicationModelConvention(areaName, folderPath, model => model.Filters.Add(anonymousFilter));
|
||||
conventions.AddAreaFolderApplicationModelConvention(areaName, folderPath, model =>
|
||||
{
|
||||
if (conventions.MvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
model.EndpointMetadata.Add(new AllowAnonymousAttribute());
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Filters.Add(new AllowAnonymousFilter());
|
||||
}
|
||||
});
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to the page with the specified name.
|
||||
/// Requires authorization with the specified policy for the page with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="pageName">The page name.</param>
|
||||
|
|
@ -224,13 +261,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
|
||||
}
|
||||
|
||||
var authorizeFilter = new AuthorizeFilter(policy);
|
||||
conventions.AddPageApplicationModelConvention(pageName, model => model.Filters.Add(authorizeFilter));
|
||||
conventions.AddPageApplicationModelConvention(pageName, model =>
|
||||
{
|
||||
if (conventions.MvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
model.EndpointMetadata.Add(new AuthorizeAttribute(policy));
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Filters.Add(new AuthorizeFilter(policy));
|
||||
}
|
||||
});
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthorizeFilter"/> to the page with the specified name.
|
||||
/// Requires authorization for the specified page.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="pageName">The page name.</param>
|
||||
|
|
@ -239,7 +285,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
AuthorizePage(conventions, pageName, policy: string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthorizeFilter"/> with default policy to the page with the specified name.
|
||||
/// Requires authorization for the specified area page.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="areaName">The area name.</param>
|
||||
|
|
@ -255,7 +301,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
=> AuthorizeAreaPage(conventions, areaName, pageName, policy: string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to the page with the specified name.
|
||||
/// Requires authorization for the specified area page with the specified policy.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="areaName">The area name.</param>
|
||||
|
|
@ -289,13 +335,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
|
||||
}
|
||||
|
||||
var authorizeFilter = new AuthorizeFilter(policy);
|
||||
conventions.AddAreaPageApplicationModelConvention(areaName, pageName, model => model.Filters.Add(authorizeFilter));
|
||||
conventions.AddAreaPageApplicationModelConvention(areaName, pageName, model =>
|
||||
{
|
||||
if (conventions.MvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
model.EndpointMetadata.Add(new AuthorizeAttribute(policy));
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Filters.Add(new AuthorizeFilter(policy));
|
||||
}
|
||||
});
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to all pages under the specified folder.
|
||||
/// Requires authorization for all pages under the specified folder.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="folderPath">The folder path.</param>
|
||||
|
|
@ -313,13 +368,22 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath));
|
||||
}
|
||||
|
||||
var authorizeFilter = new AuthorizeFilter(policy);
|
||||
conventions.AddFolderApplicationModelConvention(folderPath, model => model.Filters.Add(authorizeFilter));
|
||||
conventions.AddFolderApplicationModelConvention(folderPath, model =>
|
||||
{
|
||||
if (conventions.MvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
model.EndpointMetadata.Add(new AuthorizeAttribute(policy));
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Filters.Add(new AuthorizeFilter(policy));
|
||||
}
|
||||
});
|
||||
return conventions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthorizeFilter"/> to all pages under the specified folder.
|
||||
/// Requires authorization for all pages under the specified folder.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="folderPath">The folder path.</param>
|
||||
|
|
@ -328,7 +392,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
AuthorizeFolder(conventions, folderPath, policy: string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthorizeFilter"/> with the default policy to all pages under the specified folder.
|
||||
/// Requires authorization with the default policy for all pages under the specified folder.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="areaName">The area name.</param>
|
||||
|
|
@ -344,7 +408,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
=> AuthorizeAreaFolder(conventions, areaName, folderPath, policy: string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to all pages under the specified folder.
|
||||
/// Requires authorization with the specified policy for all pages under the specified folder.
|
||||
/// </summary>
|
||||
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
|
||||
/// <param name="areaName">The area name.</param>
|
||||
|
|
@ -378,8 +442,17 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath));
|
||||
}
|
||||
|
||||
var authorizeFilter = new AuthorizeFilter(policy);
|
||||
conventions.AddAreaFolderApplicationModelConvention(areaName, folderPath, model => model.Filters.Add(authorizeFilter));
|
||||
conventions.AddAreaFolderApplicationModelConvention(areaName, folderPath, model =>
|
||||
{
|
||||
if (conventions.MvcOptions.EnableEndpointRouting)
|
||||
{
|
||||
model.EndpointMetadata.Add(new AuthorizeAttribute(policy));
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Filters.Add(new AuthorizeFilter(policy));
|
||||
}
|
||||
});
|
||||
return conventions;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
// 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;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Microsoft.Extensions.DependencyInjection
|
||||
{
|
||||
internal class RazorPagesOptionsSetup : IConfigureOptions<RazorPagesOptions>
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public RazorPagesOptionsSetup(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
}
|
||||
|
||||
public void Configure(RazorPagesOptions options)
|
||||
{
|
||||
if (options == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
options.Conventions = new PageConventionCollection(_serviceProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
_viewCompilerProvider = viewCompilerProvider;
|
||||
_endpointFactory = endpointFactory;
|
||||
_conventions = pageOptions.Value.Conventions;
|
||||
_conventions = pageOptions.Value.Conventions ?? throw new ArgumentNullException(nameof(RazorPagesOptions.Conventions));
|
||||
_globalFilters = mvcOptions.Value.Filters;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
using Microsoft.AspNetCore.Mvc.Abstractions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
|
|||
/// Gets a collection of <see cref="IPageConvention"/> instances that are applied during
|
||||
/// route and page model construction.
|
||||
/// </summary>
|
||||
public PageConventionCollection Conventions { get; } = new PageConventionCollection();
|
||||
public PageConventionCollection Conventions { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Application relative path used as the root of discovery for Razor Page files.
|
||||
|
|
|
|||
|
|
@ -17,12 +17,14 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
public class AuthorizationPageApplicationModelProviderTest
|
||||
{
|
||||
private readonly IOptions<MvcOptions> OptionsWithoutEndpointRouting = Options.Create(new MvcOptions { EnableEndpointRouting = false });
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_IgnoresAttributesOnHandlerMethods()
|
||||
{
|
||||
// Arrange
|
||||
var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()));
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider, OptionsWithoutEndpointRouting);
|
||||
var typeInfo = typeof(PageWithAuthorizeHandlers).GetTypeInfo();
|
||||
var context = GetApplicationProviderContext(typeInfo);
|
||||
|
||||
|
|
@ -51,12 +53,31 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_DoesNothingWithEndpointRouting()
|
||||
{
|
||||
// Arrange
|
||||
var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()));
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider, Options.Create(new MvcOptions()));
|
||||
var typeInfo = typeof(TestPage).GetTypeInfo();
|
||||
var context = GetApplicationProviderContext(typeInfo);
|
||||
|
||||
// Act
|
||||
authorizationProvider.OnProvidersExecuting(context);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(
|
||||
context.PageApplicationModel.Filters,
|
||||
f => Assert.IsType<PageHandlerPageFilter>(f),
|
||||
f => Assert.IsType<HandleOptionsRequestsPageFilter>(f));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnProvidersExecuting_AddsAuthorizeFilter_IfModelHasAuthorizationAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()));
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider, OptionsWithoutEndpointRouting);
|
||||
var context = GetApplicationProviderContext(typeof(TestPage).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
|
|
@ -94,7 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
options.Value.AddPolicy("Derived", policy => policy.RequireClaim("Derived"));
|
||||
|
||||
var policyProvider = new DefaultAuthorizationPolicyProvider(options);
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider, OptionsWithoutEndpointRouting);
|
||||
|
||||
var context = GetApplicationProviderContext(typeof(TestPageWithDerivedModel).GetTypeInfo());
|
||||
|
||||
|
|
@ -138,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
{
|
||||
// Arrange
|
||||
var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions()));
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider);
|
||||
var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider, OptionsWithoutEndpointRouting);
|
||||
var context = GetApplicationProviderContext(typeof(PageWithAnonymousModel).GetTypeInfo());
|
||||
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
// Assert
|
||||
Assert.Same(actionDescriptor.ActionConstraints, actual.ActionConstraints);
|
||||
Assert.Same(actionDescriptor.AttributeRouteInfo, actual.AttributeRouteInfo);
|
||||
Assert.Same(actionDescriptor.EndpointMetadata, actual.EndpointMetadata);
|
||||
Assert.Same(actionDescriptor.RelativePath, actual.RelativePath);
|
||||
Assert.Same(actionDescriptor.RouteValues, actual.RouteValues);
|
||||
Assert.Same(actionDescriptor.ViewEnginePath, actual.ViewEnginePath);
|
||||
|
|
@ -394,6 +393,40 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateDescriptor_CombinesEndpointMetadataFromHandlerTypeAttributesAndAttributesOnModel()
|
||||
{
|
||||
// Arrange
|
||||
var metadata1 = "metadata1";
|
||||
var metadata2 = "metadata2";
|
||||
var metadata3 = "metadata3";
|
||||
var metadata4 = "metadata4";
|
||||
var metadata5 = "metadata5";
|
||||
var metadata6 = "metadata6";
|
||||
|
||||
var actionDescriptor = new PageActionDescriptor
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>(),
|
||||
AttributeRouteInfo = new AttributeRouteInfo(),
|
||||
EndpointMetadata = new List<object> { metadata3, metadata4, },
|
||||
FilterDescriptors = new List<FilterDescriptor>(),
|
||||
RelativePath = "/Foo",
|
||||
RouteValues = new Dictionary<string, string>(),
|
||||
ViewEnginePath = "/Pages/Foo",
|
||||
};
|
||||
var handlerTypeInfo = typeof(object).GetTypeInfo();
|
||||
var pageApplicationModel = new PageApplicationModel(actionDescriptor, handlerTypeInfo, new[] { metadata1, metadata2, });
|
||||
pageApplicationModel.EndpointMetadata.Add(metadata5);
|
||||
pageApplicationModel.EndpointMetadata.Add(metadata6);
|
||||
var globalFilters = new FilterCollection();
|
||||
|
||||
// Act
|
||||
var actual = CompiledPageActionDescriptorBuilder.Build(pageApplicationModel, globalFilters);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(new[] { metadata1, metadata2, metadata3, metadata4, metadata5, metadata6 }, actual.EndpointMetadata);
|
||||
}
|
||||
|
||||
private class HandlerWithIgnoredProperties
|
||||
{
|
||||
[BindProperty]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
// 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;
|
||||
using Microsoft.AspNetCore.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
||||
|
|
@ -77,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
public void RemoveType_RemovesAllOfType()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new PageConventionCollection
|
||||
var collection = new PageConventionCollection(Mock.Of<IServiceProvider>())
|
||||
{
|
||||
new FooPageConvention(),
|
||||
new BarPageConvention(),
|
||||
|
|
@ -97,7 +99,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
|
|||
public void GenericRemoveType_RemovesAllOfType()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new PageConventionCollection
|
||||
var collection = new PageConventionCollection(Mock.Of<IServiceProvider>())
|
||||
{
|
||||
new FooPageConvention(),
|
||||
new BarPageConvention(),
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
public void AddRazorPagesOptions_AddsConventions()
|
||||
{
|
||||
// Arrange
|
||||
var services = new ServiceCollection().AddOptions();
|
||||
var services = new ServiceCollection().AddOptions()
|
||||
.AddSingleton<IConfigureOptions<RazorPagesOptions>, RazorPagesOptionsSetup>();
|
||||
var applicationModelConvention = Mock.Of<IPageApplicationModelConvention>();
|
||||
var routeModelConvention = Mock.Of<IPageRouteModelConvention>();
|
||||
var builder = new MvcBuilder(services, new ApplicationPartManager());
|
||||
|
|
|
|||
|
|
@ -4,10 +4,12 @@
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -20,7 +22,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
{
|
||||
// Arrange
|
||||
var filter = Mock.Of<IFilterMetadata>();
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
|
|
@ -40,10 +42,45 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizePage_AddsAllowAnonymousFilterToSpecificPage()
|
||||
public void AuthorizePage_AddsAllowAnonymousAttributeToSpecificPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
|
||||
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizeFolder("/Users");
|
||||
conventions.AllowAnonymousToPage("/Users/Contact");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.EndpointMetadata),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Account", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Contact", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.IsType<AuthorizeAttribute>(model.EndpointMetadata[0]);
|
||||
Assert.IsType<AllowAnonymousAttribute>(model.EndpointMetadata[1]);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizePage_WithoutEndpointRouting_AddsAllowAnonymousFilterToSpecificPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
|
|
@ -73,10 +110,36 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowAnonymousToAreaPage_AddsAllowAnonymousFilterToSpecificPage()
|
||||
public void AllowAnonymousToAreaPage_AddsAllowAnonymousAttributeToSpecificPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AllowAnonymousToAreaPage("Accounts", "/Profile");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.Filters),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.IsType<AllowAnonymousAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowAnonymousToAreaPage_WithoutEndpointRouting_AddsAllowAnonymousFilterToSpecificPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
|
|
@ -100,10 +163,141 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
[Theory]
|
||||
[InlineData("/Users")]
|
||||
[InlineData("/Users/")]
|
||||
public void AuthorizePage_AddsAllowAnonymousFilterToPagesUnderFolder(string folderName)
|
||||
public void AuthorizePage_AddsAllowAnonymousAttributeToPageUnderFolder(string folderName)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
|
||||
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizeFolder("/");
|
||||
conventions.AllowAnonymousToFolder(folderName);
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Index", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.Collection(model.EndpointMetadata,
|
||||
metadata => Assert.IsType<AuthorizeAttribute>(metadata));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Account", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.Collection(model.EndpointMetadata,
|
||||
metadata => Assert.IsType<AuthorizeAttribute>(metadata),
|
||||
metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Contact", model.ViewEnginePath);
|
||||
Assert.Collection(model.EndpointMetadata,
|
||||
metadata => Assert.IsType<AuthorizeAttribute>(metadata),
|
||||
metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Users")]
|
||||
[InlineData("/Users/")]
|
||||
public void AuthorizePage_WithoutEndpointRouting_AddsAllowAnonymousFilterToPageUnderFolder(string folderName)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
|
||||
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizeFolder("/");
|
||||
conventions.AllowAnonymousToFolder(folderName);
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Index", model.ViewEnginePath);
|
||||
Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Account", model.ViewEnginePath);
|
||||
Assert.IsType<AuthorizeFilter>(model.Filters[0]);
|
||||
Assert.IsType<AllowAnonymousFilter>(model.Filters[1]);
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Contact", model.ViewEnginePath);
|
||||
Assert.IsType<AuthorizeFilter>(model.Filters[0]);
|
||||
Assert.IsType<AllowAnonymousFilter>(model.Filters[1]);
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Users")]
|
||||
[InlineData("/Users/")]
|
||||
public void AuthorizePage_AddsAllowAnonymousAttributeToPagesUnderFolder(string folderName)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
|
||||
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizeFolder("/");
|
||||
conventions.AllowAnonymousToFolder("/Users");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Index", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.Collection(model.EndpointMetadata,
|
||||
metadata => Assert.IsType<AuthorizeAttribute>(metadata));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Account", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.Collection(model.EndpointMetadata,
|
||||
metadata => Assert.IsType<AuthorizeAttribute>(metadata),
|
||||
metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Contact", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.Collection(model.EndpointMetadata,
|
||||
metadata => Assert.IsType<AuthorizeAttribute>(metadata),
|
||||
metadata => Assert.IsType<AllowAnonymousAttribute>(metadata));
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("/Users")]
|
||||
[InlineData("/Users/")]
|
||||
public void AuthorizePage_WithoutEndpointRouting_AddsAllowAnonymousFilterToPagesUnderFolder(string folderName)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
|
|
@ -138,10 +332,47 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowAnonymousToAreaFolder_AddsAllowAnonymousFilterToFolderInArea()
|
||||
public void AllowAnonymousToAreaFolder_AddsEndpointMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AllowAnonymousToAreaFolder("Accounts", "/Manage");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.EndpointMetadata),
|
||||
model => Assert.Empty(model.EndpointMetadata),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.IsType<AllowAnonymousAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
Assert.IsType<AllowAnonymousAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
},
|
||||
model => Assert.Empty(model.EndpointMetadata));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowAnonymousToAreaFolder_WithoutEndpointRouting_AddsAllowAnonymousFilterToFolderInArea()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
|
|
@ -173,10 +404,39 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizePage_AddsAuthorizeFilterWithPolicyToSpecificPage()
|
||||
public void AuthorizePage_AddsAuthorizeAttributeWithPolicyToSpecificPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
|
||||
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizePage("/Users/Account", "Manage-Accounts");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.Filters),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Account", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Equal("Manage-Accounts", authorizeData.Policy);
|
||||
},
|
||||
model => Assert.Empty(model.Filters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizePage_WithoutEndpointRouting_AddsAuthorizeFilterWithPolicyToSpecificPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
|
|
@ -202,10 +462,37 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeAreaPage_AddsAuthorizeFilterWithDefaultPolicyToAreaPage()
|
||||
public void AuthorizeAreaPage_AddsAuthorizeAttributeWithDefaultPolicyToAreaPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizeAreaPage("Accounts", "/Profile");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.Filters),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeAttribute = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Empty(authorizeAttribute.Policy);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeAreaPage_WithoutEndpointRouting_AddsAuthorizeFilterWithDefaultPolicyToAreaPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
|
|
@ -229,10 +516,37 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeAreaPage_AddsAuthorizeFilterWithCustomPolicyToAreaPage()
|
||||
public void AuthorizeAreaPage_AddsAuthorizeAttributeWithCustomPolicyToAreaPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Profile.cshtml", "/Profile", "Accounts"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizeAreaPage("Accounts", "/Profile", "custom");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.Filters),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Profile.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeAttribute = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Equal("custom", authorizeAttribute.Policy);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeAreaPage_WithoutEndpointRouting_AddsAuthorizeFilterWithCustomPolicyToAreaPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
|
|
@ -256,10 +570,39 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizePage_AddsAuthorizeFilterWithoutPolicyToSpecificPage()
|
||||
public void AuthorizePage_AddsAuthorizeAttributeWithoutPolicyToSpecificPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"),
|
||||
CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizePage("/Users/Account");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.Filters),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Account", model.ViewEnginePath);
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Equal(string.Empty, authorizeData.Policy);
|
||||
},
|
||||
model => Assert.Empty(model.Filters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizePage_WithoutEndpointRouting_AddsAuthorizeFilterWithoutPolicyToSpecificPage()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
|
|
@ -287,10 +630,10 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
[Theory]
|
||||
[InlineData("/Users")]
|
||||
[InlineData("/Users/")]
|
||||
public void AuthorizePage_AddsAuthorizeFilterWithPolicyToPagesUnderFolder(string folderName)
|
||||
public void AuthorizePage_WithoutEndpointRouting_AddsAuthorizeFilterWithPolicyToPagesUnderFolder(string folderName)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index"),
|
||||
|
|
@ -308,15 +651,15 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Account", model.ViewEnginePath);
|
||||
var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Equal("Manage-Accounts", authorizeData.Policy);
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Users/Contact", model.ViewEnginePath);
|
||||
var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(model.Filters));
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(authorizeFilter.AuthorizeData));
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Equal("Manage-Accounts", authorizeData.Policy);
|
||||
});
|
||||
}
|
||||
|
|
@ -324,10 +667,10 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
[Theory]
|
||||
[InlineData("/Users")]
|
||||
[InlineData("/Users/")]
|
||||
public void AuthorizePage_AddsAuthorizeFilterWithoutPolicyToPagesUnderFolder(string folderName)
|
||||
public void AuthorizePage_WithoutEndpointRouting_AddsAuthorizeFilterWithoutPolicyToPagesUnderFolder(string folderName)
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"),
|
||||
|
|
@ -359,10 +702,49 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeAreaFolder_AddsAuthorizeFilterWithDefaultPolicyToAreaPagesInFolder()
|
||||
public void AuthorizeAreaFolder_AddsAuthorizeAttributeWithDefaultPolicyToAreaPagesInFolder()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizeAreaFolder("Accounts", "/Manage");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.Filters),
|
||||
model => Assert.Empty(model.Filters),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Empty(authorizeData.Policy);
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Empty(authorizeData.Policy);
|
||||
},
|
||||
model => Assert.Empty(model.Filters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeAreaFolder_WithoutEndpointRouting_AddsAuthorizeFilterWithDefaultPolicyToAreaPagesInFolder()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
|
|
@ -398,10 +780,49 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeAreaFolder_AddsAuthorizeFilterWithCustomPolicyToAreaPagesInFolder()
|
||||
public void AuthorizeAreaFolder_AddsAuthorizeAttributeWithCustomPolicyToAreaPagesInFolder()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
CreateApplicationModel("/Mange/Profile.cshtml", "/Manage/Profile"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Manage/Profile.cshtml", "/Manage/Profile", "Accounts"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/Manage/2FA.cshtml", "/Manage/2FA", "Accounts"),
|
||||
CreateApplicationModel("/Areas/Accounts/Pages/View/OrderHistory.cshtml", "/View/OrderHistory", "Accounts"),
|
||||
};
|
||||
|
||||
// Act
|
||||
conventions.AuthorizeAreaFolder("Accounts", "/Manage", "custom");
|
||||
ApplyConventions(conventions, models);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(models,
|
||||
model => Assert.Empty(model.EndpointMetadata),
|
||||
model => Assert.Empty(model.EndpointMetadata),
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Manage/Profile.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Equal("custom", authorizeData.Policy);
|
||||
},
|
||||
model =>
|
||||
{
|
||||
Assert.Equal("/Areas/Accounts/Pages/Manage/2FA.cshtml", model.RelativePath);
|
||||
Assert.Empty(model.Filters);
|
||||
var authorizeData = Assert.IsType<AuthorizeAttribute>(Assert.Single(model.EndpointMetadata));
|
||||
Assert.Equal("custom", authorizeData.Policy);
|
||||
},
|
||||
model => Assert.Empty(model.Filters));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AuthorizeAreaFolder_WithoutEndpointRouting_AddsAuthorizeFilterWithCustomPolicyToAreaPagesInFolder()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = GetConventions(enableEndpointRouting: false);
|
||||
var models = new[]
|
||||
{
|
||||
CreateApplicationModel("/Profile.cshtml", "/Profile"),
|
||||
|
|
@ -440,7 +861,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
public void AddPageRoute_AddsRouteToSelector()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
new PageRouteModel("/Pages/Index.cshtml", "/Index")
|
||||
|
|
@ -502,7 +923,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
public void AddAreaPageRoute_AddsRouteToSelector()
|
||||
{
|
||||
// Arrange
|
||||
var conventions = new PageConventionCollection();
|
||||
var conventions = GetConventions();
|
||||
var models = new[]
|
||||
{
|
||||
new PageRouteModel("/Pages/Profile.cshtml", "/Profile")
|
||||
|
|
@ -544,7 +965,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
selector =>
|
||||
{
|
||||
Assert.Equal("Accounts/Profile", selector.AttributeRouteModel.Template);
|
||||
Assert.True (selector.AttributeRouteModel.SuppressLinkGeneration);
|
||||
Assert.True(selector.AttributeRouteModel.SuppressLinkGeneration);
|
||||
},
|
||||
selector =>
|
||||
{
|
||||
|
|
@ -554,6 +975,15 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
});
|
||||
}
|
||||
|
||||
private PageConventionCollection GetConventions(bool enableEndpointRouting = true)
|
||||
{
|
||||
var options = new MvcOptions { EnableEndpointRouting = enableEndpointRouting };
|
||||
var serviceProvider = new ServiceCollection()
|
||||
.AddSingleton<IOptions<MvcOptions>>(Options.Options.Create(options))
|
||||
.BuildServiceProvider();
|
||||
return new PageConventionCollection(serviceProvider);
|
||||
}
|
||||
|
||||
private static SelectorModel CreateSelectorModel(string template, bool suppressLinkGeneration = false)
|
||||
{
|
||||
return new SelectorModel
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
{
|
||||
public class DefaultPageLoaderTest
|
||||
{
|
||||
private readonly IOptions<RazorPagesOptions> RazorPagesOptions = Options.Create(new RazorPagesOptions { Conventions = new PageConventionCollection(Mock.Of<IServiceProvider>()) });
|
||||
private readonly IActionDescriptorCollectionProvider ActionDescriptorCollectionProvider;
|
||||
|
||||
public DefaultPageLoaderTest()
|
||||
|
|
@ -36,7 +37,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
var compilerProvider = GetCompilerProvider();
|
||||
|
||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||
var mvcOptions = Options.Create(new MvcOptions());
|
||||
var endpointFactory = new ActionEndpointFactory(Mock.Of<RoutePatternTransformer>());
|
||||
|
||||
|
|
@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
razorPagesOptions,
|
||||
RazorPagesOptions,
|
||||
mvcOptions);
|
||||
|
||||
// Act
|
||||
|
|
@ -121,7 +121,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
var compilerProvider = GetCompilerProvider();
|
||||
|
||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||
var mvcOptions = Options.Create(new MvcOptions());
|
||||
var endpointFactory = new ActionEndpointFactory(transformer.Object);
|
||||
|
||||
|
|
@ -147,7 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
razorPagesOptions,
|
||||
RazorPagesOptions,
|
||||
mvcOptions);
|
||||
|
||||
// Act
|
||||
|
|
@ -163,7 +162,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
// Arrange
|
||||
var descriptor = new PageActionDescriptor();
|
||||
var compilerProvider = GetCompilerProvider();
|
||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||
var mvcOptions = Options.Create(new MvcOptions());
|
||||
var endpointFactory = new ActionEndpointFactory(Mock.Of<RoutePatternTransformer>());
|
||||
|
||||
|
|
@ -212,7 +210,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
razorPagesOptions,
|
||||
RazorPagesOptions,
|
||||
mvcOptions);
|
||||
|
||||
// Act
|
||||
|
|
@ -242,7 +240,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
var compilerProvider = GetCompilerProvider();
|
||||
|
||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||
var mvcOptions = Options.Create(new MvcOptions());
|
||||
var endpointFactory = new ActionEndpointFactory(transformer.Object);
|
||||
|
||||
|
|
@ -268,7 +265,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
razorPagesOptions,
|
||||
RazorPagesOptions,
|
||||
mvcOptions);
|
||||
|
||||
// Act
|
||||
|
|
@ -298,7 +295,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
var compilerProvider = GetCompilerProvider();
|
||||
|
||||
var razorPagesOptions = Options.Create(new RazorPagesOptions());
|
||||
var mvcOptions = Options.Create(new MvcOptions());
|
||||
var endpointFactory = new ActionEndpointFactory(transformer.Object);
|
||||
|
||||
|
|
@ -333,7 +329,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
providers,
|
||||
compilerProvider,
|
||||
endpointFactory,
|
||||
razorPagesOptions,
|
||||
RazorPagesOptions,
|
||||
mvcOptions);
|
||||
|
||||
// Act
|
||||
|
|
@ -358,7 +354,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Same(model, m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>())
|
||||
{
|
||||
convention.Object,
|
||||
};
|
||||
|
|
@ -392,7 +388,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Same(model, m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>())
|
||||
{
|
||||
globalConvention.Object,
|
||||
};
|
||||
|
|
@ -424,7 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Same(handlerModel, m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection();
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>());
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
@ -451,7 +447,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Same(handlerModel, m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection { handlerModelConvention.Object };
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>()) { handlerModelConvention.Object };
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
@ -482,7 +478,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
m.Page.HandlerMethods.Remove(m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection();
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>());
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
@ -513,7 +509,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Same(parameterModel, m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection();
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>());
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
@ -544,7 +540,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Same(parameterModel, m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection { parameterModelConvention.Object };
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>()) { parameterModelConvention.Object };
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
@ -579,7 +575,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
model.Handler.Parameters.Remove(model);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection();
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>());
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
@ -613,7 +609,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Same(propertyModel, m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection();
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>());
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
@ -644,7 +640,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
Assert.Same(propertyModel, m);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection { propertyModelConvention.Object };
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>()) { propertyModelConvention.Object };
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
@ -676,7 +672,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
model.Page.HandlerProperties.Remove(model);
|
||||
})
|
||||
.Verifiable();
|
||||
var conventionCollection = new PageConventionCollection();
|
||||
var conventionCollection = new PageConventionCollection(Mock.Of<IServiceProvider>());
|
||||
|
||||
// Act
|
||||
DefaultPageLoader.ApplyConventions(conventionCollection, applicationModel);
|
||||
|
|
|
|||
|
|
@ -371,7 +371,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
|
|||
|
||||
private static IOptions<RazorPagesOptions> GetRazorPagesOptions()
|
||||
{
|
||||
return Options.Create(new RazorPagesOptions());
|
||||
return Options.Create(new RazorPagesOptions { Conventions = new PageConventionCollection(Mock.Of<IServiceProvider>()) });
|
||||
}
|
||||
|
||||
private class TestPageRouteModelProvider : IPageRouteModelProvider
|
||||
|
|
|
|||
|
|
@ -360,13 +360,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
|
|||
new ConsumesAttribute("text/json"),
|
||||
},
|
||||
},
|
||||
new ActionDescriptor()
|
||||
{
|
||||
ActionConstraints = new List<IActionConstraintMetadata>()
|
||||
{
|
||||
new CorsHttpMethodActionConstraint(new HttpMethodActionConstraint(new[]{ "GET", })),
|
||||
},
|
||||
},
|
||||
};
|
||||
var endpoints = actions.Select(CreateEndpoint).ToArray();
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class AuthMiddlewareAndFilterTest : AuthMiddlewareAndFilterTestBase<SecurityWebSite.StartupWithGlobalAuthFilter>
|
||||
{
|
||||
public AuthMiddlewareAndFilterTest(MvcTestFixture<SecurityWebSite.StartupWithGlobalAuthFilter> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
// 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.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public abstract class AuthMiddlewareAndFilterTestBase<TStartup> : IClassFixture<MvcTestFixture<TStartup>> where TStartup : class
|
||||
{
|
||||
protected AuthMiddlewareAndFilterTestBase(MvcTestFixture<TStartup> fixture)
|
||||
{
|
||||
var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder);
|
||||
Client = factory.CreateDefaultClient();
|
||||
}
|
||||
|
||||
private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => builder.UseStartup<TStartup>();
|
||||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact]
|
||||
public async Task AllowAnonymousOnActionsWork()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("AuthorizedActions/ActionWithoutAllowAnonymous");
|
||||
|
||||
// Assert
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GlobalAuthFilter_AppliesToActionsWithoutAnyAuthAttributes()
|
||||
{
|
||||
var action = "AuthorizedActions/ActionWithoutAuthAttribute";
|
||||
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 GlobalAuthFilter_CombinesWithAuthAttributeSpecifiedOnAction()
|
||||
{
|
||||
var action = "AuthorizedActions/ActionWithAuthAttribute";
|
||||
var response = await Client.GetAsync(action);
|
||||
|
||||
await AssertAuthorizeResponse(response);
|
||||
|
||||
// LoginClaimA should be enough for the global auth filter, but not for the auth attribute on the action.
|
||||
var authCookie = await GetAuthCookieAsync("LoginClaimA");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, action);
|
||||
request.Headers.Add("Cookie", authCookie);
|
||||
|
||||
response = await Client.SendAsync(request);
|
||||
await AssertAuthorizeResponse(response);
|
||||
|
||||
authCookie = await GetAuthCookieAsync("LoginClaimAB");
|
||||
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 AllowAnonymousOnPageConfiguredViaConventionWorks()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("AllowAnonymousPageViaConvention");
|
||||
|
||||
// Assert
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AllowAnonymousOnPageConfiguredViaModelWorks()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("AllowAnonymousPageViaModel");
|
||||
|
||||
// Assert
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GlobalAuthFilterAppliedToPageWorks()
|
||||
{
|
||||
// Arrange & Act
|
||||
var response = await Client.GetAsync("PagesHome");
|
||||
|
||||
// Assert
|
||||
await AssertAuthorizeResponse(response);
|
||||
|
||||
// We should be able to login with ClaimA alone
|
||||
var authCookie = await GetAuthCookieAsync("LoginClaimA");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "PagesHome");
|
||||
request.Headers.Add("Cookie", authCookie);
|
||||
|
||||
response = await Client.SendAsync(request);
|
||||
await response.AssertStatusCodeAsync(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanLoginWithBearer()
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Api");
|
||||
var response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
|
||||
var token = await GetBearerTokenAsync();
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Api");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanLoginWithBearerAfterAnonymous()
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/AllowAnonymous");
|
||||
var response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Api");
|
||||
response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
|
||||
var token = await GetBearerTokenAsync();
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Api");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanLoginWithCookie()
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Cookie");
|
||||
var response = await Client.SendAsync(request);
|
||||
await AssertAuthorizeResponse(response);
|
||||
|
||||
var cookie = await GetAuthCookieAsync("LoginDefaultScheme");
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Cookie");
|
||||
request.Headers.Add("Cookie", cookie);
|
||||
response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanLoginWithCookieAfterAnonymous()
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/AllowAnonymous");
|
||||
var response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Cookie");
|
||||
response = await Client.SendAsync(request);
|
||||
await AssertAuthorizeResponse(response);
|
||||
|
||||
var cookie = await GetAuthCookieAsync("LoginDefaultScheme");
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Cookie");
|
||||
request.Headers.Add("Cookie", cookie);
|
||||
response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanLoginWithBearerAfterCookie()
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Cookie");
|
||||
var response = await Client.SendAsync(request);
|
||||
await AssertAuthorizeResponse(response);
|
||||
|
||||
var cookie = await GetAuthCookieAsync("LoginDefaultScheme");
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Cookie");
|
||||
request.Headers.Add("Cookie", cookie);
|
||||
response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Api");
|
||||
request.Headers.Add("Cookie", cookie);
|
||||
response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
|
||||
var token = await GetBearerTokenAsync();
|
||||
|
||||
request = new HttpRequestMessage(HttpMethod.Get, "/Authorized/Api");
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
request.Headers.Add("Cookie", cookie);
|
||||
response = await Client.SendAsync(request);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task GlobalAuthFilter_CombinesWithAuthAttributeOnPageModel()
|
||||
{
|
||||
// Arrange
|
||||
var page = "AuthorizePageViaModel";
|
||||
|
||||
return LoginAB(page);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public Task GlobalAuthFilter_CombinesWithAuthAttributeSpecifiedViaConvention()
|
||||
{
|
||||
// Arrange
|
||||
var page = "AuthorizePageViaConvention";
|
||||
|
||||
return LoginAB(page);
|
||||
}
|
||||
|
||||
private async Task LoginAB(string url)
|
||||
{
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
// Assert
|
||||
await AssertAuthorizeResponse(response);
|
||||
|
||||
// ClaimA should be insufficient
|
||||
var authCookie = await GetAuthCookieAsync("LoginClaimA");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
request.Headers.Add("Cookie", authCookie);
|
||||
|
||||
response = await Client.SendAsync(request);
|
||||
await AssertAuthorizeResponse(response);
|
||||
|
||||
authCookie = await GetAuthCookieAsync("LoginClaimAB");
|
||||
request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
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("/Account/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();
|
||||
}
|
||||
|
||||
private async Task<string> GetBearerTokenAsync()
|
||||
{
|
||||
var response = await Client.GetAsync("/Login/LoginBearerClaimA");
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
||||
{
|
||||
public class AuthMiddlewareAndFilterWithoutEndpointRoutingTest : AuthMiddlewareAndFilterTestBase<SecurityWebSite.StartupWithGlobalAuthFilterWithoutEndpointRouting>
|
||||
{
|
||||
public AuthMiddlewareAndFilterWithoutEndpointRoutingTest(MvcTestFixture<SecurityWebSite.StartupWithGlobalAuthFilterWithoutEndpointRouting> fixture)
|
||||
: base(fixture)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
|
||||
// Assert
|
||||
// MVC applied the policy and since that did not pass, there were no access control headers.
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.Collection(
|
||||
response.Headers.OrderBy(h => h.Key),
|
||||
h =>
|
||||
|
|
@ -187,7 +187,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
var responseHeaders = response.Headers;
|
||||
Assert.Equal(
|
||||
new[] { "http://example.com" },
|
||||
|
|
@ -273,7 +273,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
|
||||
// Assert
|
||||
// Since there are no response headers, the client should step in to block the content.
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
|
||||
// Nothing gets executed for a pre-flight request.
|
||||
|
|
@ -297,7 +297,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
var responseHeaders = response.Headers;
|
||||
Assert.Equal(
|
||||
new[] { "*" },
|
||||
|
|
@ -329,7 +329,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
var responseHeaders = response.Headers;
|
||||
Assert.Equal(
|
||||
new[] { "http://example.com" },
|
||||
|
|
@ -351,10 +351,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
[Fact]
|
||||
public async Task DisableCorsFilter_RunsBeforeOtherAuthorizationFilters()
|
||||
{
|
||||
// Controller has an authorization filter and Cors filter and the action has a DisableCors filter
|
||||
// In this scenario, the CorsFilter should be executed before any other authorization filters
|
||||
// i.e irrespective of where the Cors filter is applied(controller or action), Cors filters must
|
||||
// always be executed before any other type of authorization filters.
|
||||
// Controller enables authorization and Cors, the action has a DisableCorsAttribute.
|
||||
// We expect the CorsMiddleware to execute and no-op
|
||||
|
||||
// Arrange
|
||||
var request = new HttpRequestMessage(
|
||||
|
|
@ -370,7 +368,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
|
||||
// Nothing gets executed for a pre-flight request.
|
||||
|
|
@ -393,7 +391,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
|
||||
Assert.Empty(response.Headers);
|
||||
|
||||
// Nothing gets executed for a pre-flight request.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Xunit;
|
||||
|
|
@ -21,11 +20,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
builder.UseStartup<SecurityWebSite.StartupWithGlobalDenyAnonymousFilter>();
|
||||
|
||||
public WebApplicationFactory<SecurityWebSite.StartupWithGlobalDenyAnonymousFilter> Factory { get; }
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8387")]
|
||||
public override Task DeniesAnonymousUsers_ByDefault()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
return response;
|
||||
}
|
||||
|
||||
string responseContent = null;
|
||||
string responseContent = string.Join(Environment.NewLine, response.Headers);
|
||||
try
|
||||
{
|
||||
responseContent = await response.Content.ReadAsStringAsync();
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("Hello from /Admin/RouteTemplate my-user-id 4", content.Trim());
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8387")]
|
||||
[Fact]
|
||||
public async Task AuthConvention_IsAppliedOnBasePathRelativePaths_ForFiles()
|
||||
{
|
||||
// Act
|
||||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("/Login?ReturnUrl=%2FConventions%2FAuth", response.Headers.Location.PathAndQuery);
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8387")]
|
||||
[Fact]
|
||||
public async Task AuthConvention_IsAppliedOnBasePathRelativePaths_For_Folders()
|
||||
{
|
||||
// Act
|
||||
|
|
@ -372,7 +372,7 @@ Hello from /Pages/Shared/";
|
|||
Assert.Equal(expected, response.Trim(), ignoreLineEndingDifferences: true);
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8387")]
|
||||
[Fact]
|
||||
public async Task AuthorizeFolderConvention_CanBeAppliedToAreaPages()
|
||||
{
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
|
||||
public HttpClient Client { get; }
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8387")]
|
||||
[Fact]
|
||||
public async Task Authorize_AppliedUsingConvention_Works()
|
||||
{
|
||||
// Act
|
||||
|
|
@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
Assert.Equal("Hello from Anonymous", content.Trim());
|
||||
}
|
||||
|
||||
[Fact(Skip = "https://github.com/aspnet/AspNetCore/issues/8387")]
|
||||
[Fact]
|
||||
public async Task Authorize_AppliedUsingAttributeOnModel_Works()
|
||||
{
|
||||
// Act
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ namespace CorsWebSite
|
|||
public virtual void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseRouting();
|
||||
app.UseCors();
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace SecurityWebSite
|
||||
{
|
||||
public static class BearerAuth
|
||||
{
|
||||
static BearerAuth()
|
||||
{
|
||||
Key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(new string('a', 128)));
|
||||
Credentials = new SigningCredentials(Key, SecurityAlgorithms.HmacSha256);
|
||||
}
|
||||
|
||||
public static readonly SymmetricSecurityKey Key;
|
||||
public static readonly SigningCredentials Credentials;
|
||||
public static readonly string Issuer = "issuer.contoso.com";
|
||||
public static readonly string Audience = "audience.contoso.com";
|
||||
|
||||
public static TokenValidationParameters CreateTokenValidationParameters()
|
||||
{
|
||||
return new TokenValidationParameters()
|
||||
{
|
||||
ValidIssuer = Issuer,
|
||||
ValidAudience = Audience,
|
||||
IssuerSigningKey = Key,
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetTokenText(IEnumerable<Claim> claims)
|
||||
{
|
||||
var token = new JwtSecurityToken(Issuer, Audience, claims, expires: DateTime.Now.AddMinutes(30), signingCredentials: Credentials);
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
// 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.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SecurityWebSite.Controllers
|
||||
{
|
||||
public class AuthorizedActionsController : ControllerBase
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public IActionResult ActionWithoutAllowAnonymous() => Ok();
|
||||
|
||||
public IActionResult ActionWithoutAuthAttribute() => Ok();
|
||||
|
||||
[Authorize("RequireClaimB")]
|
||||
public IActionResult ActionWithAuthAttribute() => Ok();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// 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.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SecurityWebSite.Controllers
|
||||
{
|
||||
[Authorize] // requires any authenticated user (aka the application cookie typically)
|
||||
public class AuthorizedController : ControllerBase
|
||||
{
|
||||
[Authorize(AuthenticationSchemes = "Bearer")]
|
||||
public IActionResult Api() => Ok();
|
||||
|
||||
public IActionResult Cookie() => Ok();
|
||||
|
||||
[AllowAnonymous]
|
||||
public IActionResult AllowAnonymous() => Ok();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// 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.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace SecurityWebSite.Controllers
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class LoginController : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> LoginDefaultScheme()
|
||||
{
|
||||
var identity = new ClaimsIdentity(new[] { new Claim("ClaimA", "Value") }, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
await HttpContext.SignInAsync(scheme: null, new ClaimsPrincipal(identity));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> LoginClaimA()
|
||||
{
|
||||
var identity = new ClaimsIdentity(new[] { new Claim("ClaimA", "Value") });
|
||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> LoginClaimAB()
|
||||
{
|
||||
var identity = new ClaimsIdentity(new[] { new Claim("ClaimA", "Value"), new Claim("ClaimB", "Value") });
|
||||
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public IActionResult LoginBearerClaimA()
|
||||
{
|
||||
var identity = new ClaimsIdentity(new[] { new Claim("ClaimA", "Value") });
|
||||
return Content(BearerAuth.GetTokenText(identity.Claims));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@page
|
||||
@model AllowAnonymousPageViaConvention
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
// 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.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace SecurityWebSite
|
||||
{
|
||||
public class AllowAnonymousPageViaConvention : PageModel
|
||||
{
|
||||
public IActionResult OnGet() => Page();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@page
|
||||
@model AllowAnonymousPageViaModel
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// 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.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace SecurityWebSite
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class AllowAnonymousPageViaModel : PageModel
|
||||
{
|
||||
public IActionResult OnGet() => Page();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@page
|
||||
@model AuthorizePageViaConvention
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
// 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.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace SecurityWebSite
|
||||
{
|
||||
public class AuthorizePageViaConvention : PageModel
|
||||
{
|
||||
public IActionResult OnGet() => Page();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@page
|
||||
@model AuthorizePageViaModel
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// 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.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace SecurityWebSite
|
||||
{
|
||||
[Authorize("RequireClaimB")]
|
||||
public class AuthorizePageViaModel : PageModel
|
||||
{
|
||||
public IActionResult OnGet() => Page();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
@page
|
||||
Hello from PagesHome
|
||||
|
|
@ -0,0 +1 @@
|
|||
@namespace SecurityWebSite
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
<Reference Include="Microsoft.AspNetCore.Authorization.Policy" />
|
||||
<Reference Include="Microsoft.AspNetCore.Authentication.Cookies" />
|
||||
<Reference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
|
||||
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />
|
||||
<Reference Include="Microsoft.AspNetCore.Hosting" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ namespace SecurityWebSite
|
|||
{
|
||||
options.LoginPath = "/Home/Login";
|
||||
options.LogoutPath = "/Home/Logout";
|
||||
}).AddCookie("Cookie2");
|
||||
})
|
||||
.AddCookie("Cookie2");
|
||||
|
||||
services.AddScoped<IPolicyEvaluator, CountingPolicyEvaluator>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace SecurityWebSite
|
||||
{
|
||||
public class StartupWithGlobalAuthFilter
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie()
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = BearerAuth.CreateTokenValidationParameters();
|
||||
});
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("RequireClaimA", policy => policy.RequireClaim("ClaimA"));
|
||||
options.AddPolicy("RequireClaimB", policy => policy.RequireClaim("ClaimB"));
|
||||
});
|
||||
|
||||
services.AddMvc(o =>
|
||||
{
|
||||
o.Filters.Add(new AuthorizeFilter("RequireClaimA"));
|
||||
})
|
||||
.AddRazorPagesOptions(options =>
|
||||
{
|
||||
options.Conventions.AllowAnonymousToPage("/AllowAnonymousPageViaConvention");
|
||||
options.Conventions.AuthorizePage("/AuthorizePageViaConvention", "RequireClaimB");
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapDefaultControllerRoute();
|
||||
endpoints.MapRazorPages();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// 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.Builder;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace SecurityWebSite
|
||||
{
|
||||
public class StartupWithGlobalAuthFilterWithoutEndpointRouting
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie()
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = BearerAuth.CreateTokenValidationParameters();
|
||||
});
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("RequireClaimA", policy => policy.RequireClaim("ClaimA"));
|
||||
options.AddPolicy("RequireClaimB", policy => policy.RequireClaim("ClaimB"));
|
||||
});
|
||||
|
||||
services.AddMvc(o =>
|
||||
{
|
||||
o.EnableEndpointRouting = false;
|
||||
o.Filters.Add(new AuthorizeFilter("RequireClaimA"));
|
||||
})
|
||||
.AddRazorPagesOptions(options =>
|
||||
{
|
||||
options.Conventions.AllowAnonymousToPage("/AllowAnonymousPageViaConvention");
|
||||
options.Conventions.AuthorizePage("/AuthorizePageViaConvention", "RequireClaimB");
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
{
|
||||
app.UseAuthentication();
|
||||
app.UseMvcWithDefaultRoute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,12 @@ namespace SecurityWebSite
|
|||
options.LogoutPath = "/Home/Logout";
|
||||
}).AddCookie("Cookie2");
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy("RequireClaimA", policy => policy.RequireClaim("ClaimA"));
|
||||
options.AddPolicy("RequireClaimB", policy => policy.RequireClaim("ClaimB"));
|
||||
});
|
||||
|
||||
services.AddMvc(o =>
|
||||
{
|
||||
o.EnableEndpointRouting = false;
|
||||
|
|
|
|||
Loading…
Reference in New Issue