Port Throw when UseAuthorization is incorrectly configured (#14893)

* Port Throw when UseAuthorization is incorrectly configured

* fixup
This commit is contained in:
Pranav K 2019-10-10 14:14:43 -07:00 committed by Artak
parent c0a7f04370
commit 8c39137944
9 changed files with 341 additions and 38 deletions

View File

@ -24,7 +24,15 @@ namespace TestServer
services.AddMvc().AddNewtonsoftJson();
services.AddCors(options =>
{
options.AddPolicy("AllowAll", _ => { /* Controlled below */ });
// 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.
options.AddPolicy("AllowAll", policy =>
policy.SetIsOriginAllowed(host => host.StartsWith("http://localhost:") || host.StartsWith("http://127.0.0.1:"))
.AllowAnyHeader()
.WithExposedHeaders("MyCustomHeader")
.AllowAnyMethod()
.AllowCredentials());
});
services.AddServerSideBlazor()
.AddCircuitOptions(o =>
@ -49,18 +57,6 @@ namespace TestServer
app.UseDeveloperExceptionPage();
}
// 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.UseAuthentication();
// Mount the server-side Blazor app on /subdir
@ -119,6 +115,8 @@ namespace TestServer
});
app.UseRouting();
app.UseCors();
app.UseEndpoints(endpoints =>
{

View File

@ -113,6 +113,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.WebUtilities.Performance", "WebUtilities\perf\Microsoft.AspNetCore.WebUtilities.Performance\Microsoft.AspNetCore.WebUtilities.Performance.csproj", "{21AC56E7-4E77-4B0E-B63E-C8E836E4D14E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Authorization.Policy", "..\Security\Authorization\Policy\src\Microsoft.AspNetCore.Authorization.Policy.csproj", "{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Cors", "..\Middleware\CORS\src\Microsoft.AspNetCore.Cors.csproj", "{09168958-FD5B-4D25-8FBF-75E2C80D903B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -603,6 +607,30 @@ Global
{21AC56E7-4E77-4B0E-B63E-C8E836E4D14E}.Release|x64.Build.0 = Release|Any CPU
{21AC56E7-4E77-4B0E-B63E-C8E836E4D14E}.Release|x86.ActiveCfg = Release|Any CPU
{21AC56E7-4E77-4B0E-B63E-C8E836E4D14E}.Release|x86.Build.0 = Release|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Debug|x64.ActiveCfg = Debug|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Debug|x64.Build.0 = Debug|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Debug|x86.ActiveCfg = Debug|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Debug|x86.Build.0 = Debug|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Release|Any CPU.Build.0 = Release|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Release|x64.ActiveCfg = Release|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Release|x64.Build.0 = Release|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Release|x86.ActiveCfg = Release|Any CPU
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B}.Release|x86.Build.0 = Release|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Debug|x64.ActiveCfg = Debug|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Debug|x64.Build.0 = Debug|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Debug|x86.ActiveCfg = Debug|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Debug|x86.Build.0 = Debug|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Release|Any CPU.Build.0 = Release|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Release|x64.ActiveCfg = Release|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Release|x64.Build.0 = Release|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Release|x86.ActiveCfg = Release|Any CPU
{09168958-FD5B-4D25-8FBF-75E2C80D903B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -651,6 +679,8 @@ Global
{611794D2-EF3A-422A-A077-23E61C7ADE49} = {793FFE24-138A-4C3D-81AB-18D625E36230}
{1062FCDE-E145-40EC-B175-FDBCAA0C59A0} = {793FFE24-138A-4C3D-81AB-18D625E36230}
{21AC56E7-4E77-4B0E-B63E-C8E836E4D14E} = {80A090C8-ED02-4DE3-875A-30DCCDBD84BA}
{8BCAA9EC-0ACD-435C-BF8A-8C843499FF7B} = {793FFE24-138A-4C3D-81AB-18D625E36230}
{09168958-FD5B-4D25-8FBF-75E2C80D903B} = {793FFE24-138A-4C3D-81AB-18D625E36230}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {85B5E151-2E9D-419C-83DD-0DDCF446C83A}

View File

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -14,8 +13,8 @@ namespace Microsoft.AspNetCore.Routing
{
internal sealed class EndpointMiddleware
{
internal const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareInvoked";
internal const string CorsMiddlewareInvokedKey = "__CorsMiddlewareInvoked";
internal const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareWithEndpointInvoked";
internal const string CorsMiddlewareInvokedKey = "__CorsMiddlewareWithEndpointInvoked";
private readonly ILogger _logger;
private readonly RequestDelegate _next;
@ -91,7 +90,7 @@ namespace Microsoft.AspNetCore.Routing
throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains authorization metadata, " +
"but a middleware was not found that supports authorization." +
Environment.NewLine +
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code.");
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).");
}
private static void ThrowMissingCorsMiddlewareException(Endpoint endpoint)
@ -99,7 +98,7 @@ namespace Microsoft.AspNetCore.Routing
throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains CORS metadata, " +
"but a middleware was not found that supports CORS." +
Environment.NewLine +
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code.");
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code. The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).");
}
private static class Log

View File

@ -0,0 +1,240 @@
// 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.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Xunit;
namespace Microsoft.AspNetCore.Routing.FunctionalTests
{
public class EndpointRoutingIntegrationTest
{
private static readonly RequestDelegate TestDelegate = async context => await Task.Yield();
private static readonly string AuthErrorMessage = "Endpoint / contains authorization metadata, but a middleware was not found that supports authorization." +
Environment.NewLine +
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. " +
"The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).";
private static readonly string CORSErrorMessage = "Endpoint / contains CORS metadata, but a middleware was not found that supports CORS." +
Environment.NewLine +
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code. " +
"The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).";
[Fact]
public async Task AuthorizationMiddleware_WhenNoAuthMetadataIsConfigured()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(b => b.Map("/", TestDelegate));
})
.ConfigureServices(services =>
{
services.AddAuthorization();
services.AddRouting();
});
using var server = new TestServer(builder);
var response = await server.CreateRequest("/").SendAsync("GET");
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task AuthorizationMiddleware_WhenEndpointIsNotFound()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(b => b.Map("/", TestDelegate));
})
.ConfigureServices(services =>
{
services.AddAuthorization();
services.AddRouting();
});
using var server = new TestServer(builder);
var response = await server.CreateRequest("/not-found").SendAsync("GET");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task AuthorizationMiddleware_WithAuthorizedEndpoint()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
})
.ConfigureServices(services =>
{
services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
services.AddRouting();
});
using var server = new TestServer(builder);
var response = await server.CreateRequest("/").SendAsync("GET");
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task AuthorizationMiddleware_NotConfigured_Throws()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
})
.ConfigureServices(services =>
{
services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
services.AddRouting();
});
using var server = new TestServer(builder);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
Assert.Equal(AuthErrorMessage, ex.Message);
}
[Fact]
public async Task AuthorizationMiddleware_NotConfigured_WhenEndpointIsNotFound()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
})
.ConfigureServices(services =>
{
services.AddRouting();
});
using var server = new TestServer(builder);
var response = await server.CreateRequest("/not-found").SendAsync("GET");
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task AuthorizationMiddleware_ConfiguredBeforeRouting_Throws()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseAuthorization();
app.UseRouting();
app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
})
.ConfigureServices(services =>
{
services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
services.AddRouting();
});
using var server = new TestServer(builder);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
Assert.Equal(AuthErrorMessage, ex.Message);
}
[Fact]
public async Task AuthorizationMiddleware_ConfiguredAfterRouting_Throws()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(b => b.Map("/", TestDelegate).RequireAuthorization());
app.UseAuthorization();
})
.ConfigureServices(services =>
{
services.AddAuthorization(options => options.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build());
services.AddRouting();
});
using var server = new TestServer(builder);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
Assert.Equal(AuthErrorMessage, ex.Message);
}
[Fact]
public async Task CorsMiddleware_WithCorsEndpoint()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseRouting();
app.UseCors();
app.UseEndpoints(b => b.Map("/", TestDelegate).RequireCors(policy => policy.AllowAnyOrigin()));
})
.ConfigureServices(services =>
{
services.AddCors();
services.AddRouting();
});
using var server = new TestServer(builder);
var response = await server.CreateRequest("/").SendAsync("PUT");
response.EnsureSuccessStatusCode();
}
[Fact]
public async Task CorsMiddleware_ConfiguredBeforeRouting_Throws()
{
// Arrange
var builder = new WebHostBuilder();
builder.Configure(app =>
{
app.UseCors();
app.UseRouting();
app.UseEndpoints(b => b.Map("/", TestDelegate).RequireCors(policy => policy.AllowAnyOrigin()));
})
.ConfigureServices(services =>
{
services.AddCors();
services.AddRouting();
});
using var server = new TestServer(builder);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => server.CreateRequest("/").SendAsync("GET"));
Assert.Equal(CORSErrorMessage, ex.Message);
}
}
}

View File

@ -10,6 +10,8 @@
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Authorization.Policy" />
<Reference Include="Microsoft.AspNetCore.Cors" />
<Reference Include="Microsoft.AspNetCore.Routing" />
<Reference Include="Microsoft.AspNetCore.TestHost" />
</ItemGroup>

View File

@ -101,7 +101,8 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var expected = "Endpoint Test contains authorization metadata, but a middleware was not found that supports authorization." +
Environment.NewLine +
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code.";
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. " +
"The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).";
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceProvider()
@ -197,7 +198,8 @@ namespace Microsoft.AspNetCore.Routing
// Arrange
var expected = "Endpoint Test contains CORS metadata, but a middleware was not found that supports CORS." +
Environment.NewLine +
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code.";
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code. " +
"The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).";
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceProvider()

View File

@ -14,8 +14,8 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
public class CorsMiddleware
{
// Property key is used by other systems, e.g. MVC, to check if CORS middleware has run
private const string CorsMiddlewareInvokedKey = "__CorsMiddlewareInvoked";
private static readonly object CorsMiddlewareInvokedValue = new object();
private const string CorsMiddlewareWithEndpointInvokedKey = "__CorsMiddlewareWithEndpointInvoked";
private static readonly object CorsMiddlewareWithEndpointInvokedValue = new object();
private readonly Func<object, Task> OnResponseStartingDelegate = OnResponseStarting;
private readonly RequestDelegate _next;
@ -116,14 +116,6 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
/// <inheritdoc />
public Task Invoke(HttpContext context, ICorsPolicyProvider corsPolicyProvider)
{
// Flag to indicate to other systems, that CORS middleware was run for this request
context.Items[CorsMiddlewareInvokedKey] = CorsMiddlewareInvokedValue;
if (!context.Request.Headers.ContainsKey(CorsConstants.Origin))
{
return _next(context);
}
// CORS policy resolution rules:
//
// 1. If there is an endpoint with IDisableCorsAttribute then CORS is not run
@ -131,9 +123,20 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
// there is an endpoint with IEnableCorsAttribute that has a policy name then
// fetch policy by name, prioritizing it above policy on middleware
// 3. If there is no policy on middleware then use name on middleware
var endpoint = context.GetEndpoint();
if (endpoint != null)
{
// EndpointRoutingMiddleware uses this flag to check if the CORS middleware processed CORS metadata on the endpoint.
// The CORS middleware can only make this claim if it observes an actual endpoint.
context.Items[CorsMiddlewareWithEndpointInvokedKey] = CorsMiddlewareWithEndpointInvokedValue;
}
if (!context.Request.Headers.ContainsKey(CorsConstants.Origin))
{
return _next(context);
}
// 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>();

View File

@ -918,7 +918,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
await middleware.Invoke(httpContext, mockProvider);
// Assert
Assert.Contains(httpContext.Items, item => string.Equals(item.Key as string, "__CorsMiddlewareInvoked"));
Assert.Contains(httpContext.Items, item => string.Equals(item.Key as string, "__CorsMiddlewareWithEndpointInvoked"));
}
[Fact]
@ -936,12 +936,37 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
"DefaultPolicyName");
var httpContext = new DefaultHttpContext();
httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(new EnableCorsAttribute("MetadataPolicyName"), new DisableCorsAttribute()), "Test endpoint"));
// Act
await middleware.Invoke(httpContext, mockProvider);
// Assert
Assert.Contains(httpContext.Items, item => string.Equals(item.Key as string, "__CorsMiddlewareInvoked"));
Assert.Contains(httpContext.Items, item => string.Equals(item.Key as string, "__CorsMiddlewareWithEndpointInvoked"));
}
[Fact]
public async Task Invoke_WithoutEndpoint_InvokeFlagSet()
{
// Arrange
var corsService = Mock.Of<ICorsService>();
var mockProvider = Mock.Of<ICorsPolicyProvider>();
var loggerFactory = NullLoggerFactory.Instance;
var middleware = new CorsMiddleware(
Mock.Of<RequestDelegate>(),
corsService,
loggerFactory,
"DefaultPolicyName");
var httpContext = new DefaultHttpContext();
httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" });
// Act
await middleware.Invoke(httpContext, mockProvider);
// Assert
Assert.DoesNotContain(httpContext.Items, item => string.Equals(item.Key as string, "__CorsMiddlewareWithEndpointInvoked"));
}
}
}

View File

@ -13,9 +13,9 @@ namespace Microsoft.AspNetCore.Authorization
{
public class AuthorizationMiddleware
{
// Property key is used by other systems, e.g. MVC, to check if authorization middleware has run
private const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareInvoked";
private static readonly object AuthorizationMiddlewareInvokedValue = new object();
// Property key is used by Endpoint routing to determine if Authorization has run
private const string AuthorizationMiddlewareInvokedWithEndpointKey = "__AuthorizationMiddlewareWithEndpointInvoked";
private static readonly object AuthorizationMiddlewareWithEndpointInvokedValue = new object();
private readonly RequestDelegate _next;
private readonly IAuthorizationPolicyProvider _policyProvider;
@ -35,8 +35,12 @@ namespace Microsoft.AspNetCore.Authorization
var endpoint = context.GetEndpoint();
// Flag to indicate to other systems, e.g. MVC, that authorization middleware was run for this request
context.Items[AuthorizationMiddlewareInvokedKey] = AuthorizationMiddlewareInvokedValue;
if (endpoint != null)
{
// EndpointRoutingMiddleware uses this flag to check if the Authorization middleware processed auth metadata on the endpoint.
// The Authorization middleware can only make this claim if it observes an actual endpoint.
context.Items[AuthorizationMiddlewareInvokedWithEndpointKey] = AuthorizationMiddlewareWithEndpointInvokedValue;
}
// IMPORTANT: Changes to authorization logic should be mirrored in MVC's AuthorizeFilter
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();