Merge pull request #9286 from dotnet-maestro-bot/merge/release/3.0-preview4-to-master

[automated] Merge branch 'release/3.0-preview4' => 'master'
This commit is contained in:
Pranav K 2019-04-11 16:07:47 -07:00 committed by GitHub
commit 9173c5adeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 297 additions and 42 deletions

View File

@ -1,6 +1,18 @@
// 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.Authorization
{
public partial interface IAllowAnonymous
{
}
public partial interface IAuthorizeData
{
string AuthenticationSchemes { get; set; }
string Policy { get; set; }
string Roles { get; set; }
}
}
namespace Microsoft.AspNetCore.Builder
{
public abstract partial class EndpointBuilder
@ -87,6 +99,12 @@ namespace Microsoft.AspNetCore.Builder.Extensions
public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; }
}
}
namespace Microsoft.AspNetCore.Cors.Infrastructure
{
public partial interface ICorsMetadata
{
}
}
namespace Microsoft.AspNetCore.Http
{
public abstract partial class ConnectionInfo

View File

@ -4,7 +4,7 @@
namespace Microsoft.AspNetCore.Authorization
{
/// <summary>
/// Marker interface to enable the <see cref="AllowAnonymousAttribute"/>.
/// Marker interface to allow access to anonymous users.
/// </summary>
public interface IAllowAnonymous
{

View File

@ -322,6 +322,7 @@ namespace Microsoft.AspNetCore.Routing
public System.Collections.Generic.ICollection<Microsoft.AspNetCore.Routing.EndpointDataSource> EndpointDataSources { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
public bool LowercaseQueryStrings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public bool LowercaseUrls { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public bool SuppressCheckForUnhandledSecurityMetadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
}
public partial class RouteValueEqualityComparer : System.Collections.Generic.IEqualityComparer<object>
{

View File

@ -1,33 +1,34 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.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;
namespace Microsoft.AspNetCore.Routing
{
internal sealed class EndpointMiddleware
{
internal const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareInvoked";
internal const string CorsMiddlewareInvokedKey = "__CorsMiddlewareInvoked";
private readonly ILogger _logger;
private readonly RequestDelegate _next;
private readonly RouteOptions _routeOptions;
public EndpointMiddleware(ILogger<EndpointMiddleware> logger, RequestDelegate next)
public EndpointMiddleware(
ILogger<EndpointMiddleware> logger,
RequestDelegate next,
IOptions<RouteOptions> routeOptions)
{
if (logger == null)
{
throw new ArgumentNullException(nameof(logger));
}
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
_logger = logger;
_next = next;
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_next = next ?? throw new ArgumentNullException(nameof(next));
_routeOptions = routeOptions?.Value ?? throw new ArgumentNullException(nameof(routeOptions));
}
public async Task Invoke(HttpContext httpContext)
@ -35,6 +36,24 @@ namespace Microsoft.AspNetCore.Routing
var endpoint = httpContext.Features.Get<IEndpointFeature>()?.Endpoint;
if (endpoint?.RequestDelegate != null)
{
if (_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
{
// User opted out of this check.
return;
}
if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
!httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
{
ThrowMissingAuthMiddlewareException(endpoint);
}
if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
!httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
{
ThrowMissingCorsMiddlewareException(endpoint);
}
Log.ExecutingEndpoint(_logger, endpoint);
try
@ -52,6 +71,22 @@ namespace Microsoft.AspNetCore.Routing
await _next(httpContext);
}
private static void ThrowMissingAuthMiddlewareException(Endpoint endpoint)
{
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.");
}
private static void ThrowMissingCorsMiddlewareException(Endpoint endpoint)
{
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.");
}
private static class Log
{
private static readonly Action<ILogger, string, Exception> _executingEndpoint = LoggerMessage.Define<string>(

View File

@ -28,6 +28,26 @@ namespace Microsoft.AspNetCore.Routing
/// </summary>
public bool AppendTrailingSlash { get; set; }
/// <summary>
/// Gets or sets a value that indicates if the check for unhandled security endpoint metadata is suppressed.
/// <para>
/// Endpoints can be associated with metadata such as authorization, or CORS, that needs to be
/// handled by a specific middleware to be actionable. If the middleware is not configured, such
/// metadata will go unhandled.
/// </para>
/// <para>
/// When <see langword="false"/>, prior to the execution of the endpoint, routing will verify that
/// all known security-specific metadata has been handled.
/// Setting this property to <see langword="true"/> suppresses this check.
/// </para>
/// </summary>
/// <value>Defaults to <see langword="false"/>.</value>
/// <remarks>
/// This check exists as a safeguard against accidental insecure configuration. You may suppress
/// this check if it does not match your application's requirements.
/// </remarks>
public bool SuppressCheckForUnhandledSecurityMetadata { get; set; }
private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
public IDictionary<string, Type> ConstraintMap

View File

@ -1,17 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Routing
{
public class EndpointMiddlewareTest
{
private readonly IOptions<RouteOptions> RouteOptions = Options.Create(new RouteOptions());
[Fact]
public async Task Invoke_NoFeature_NoOps()
{
@ -24,7 +30,7 @@ namespace Microsoft.AspNetCore.Routing
return Task.CompletedTask;
};
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next);
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
// Act
await middleware.Invoke(httpContext);
@ -49,7 +55,7 @@ namespace Microsoft.AspNetCore.Routing
return Task.CompletedTask;
};
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next);
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
// Act
await middleware.Invoke(httpContext);
@ -81,7 +87,7 @@ namespace Microsoft.AspNetCore.Routing
return Task.CompletedTask;
};
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next);
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
// Act
await middleware.Invoke(httpContext);
@ -90,6 +96,146 @@ namespace Microsoft.AspNetCore.Routing
Assert.True(invoked);
}
[Fact]
public async Task Invoke_WithEndpoint_ThrowsIfAuthAttributesWereFound_ButAuthMiddlewareNotInvoked()
{
// 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.";
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceProvider()
};
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
});
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => middleware.Invoke(httpContext));
// Assert
Assert.Equal(expected, ex.Message);
}
[Fact]
public async Task Invoke_WithEndpoint_WorksIfAuthAttributesWereFound_AndAuthMiddlewareInvoked()
{
// Arrange
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceProvider()
};
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
});
httpContext.Items[EndpointMiddleware.AuthorizationMiddlewareInvokedKey] = true;
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
// Act & Assert
await middleware.Invoke(httpContext);
// If we got this far, we can sound the everything's OK alarm.
}
[Fact]
public async Task Invoke_WithEndpoint_DoesNotThrowIfUnhandledAuthAttributesWereFound_ButSuppressedViaOptions()
{
// Arrange
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceProvider()
};
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
});
var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, routeOptions);
// Act & Assert
await middleware.Invoke(httpContext);
}
[Fact]
public async Task Invoke_WithEndpoint_ThrowsIfCorsMetadataWasFound_ButCorsMiddlewareNotInvoked()
{
// 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.";
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceProvider()
};
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"),
});
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => middleware.Invoke(httpContext));
// Assert
Assert.Equal(expected, ex.Message);
}
[Fact]
public async Task Invoke_WithEndpoint_WorksIfCorsMetadataWasFound_AndCorsMiddlewareInvoked()
{
// Arrange
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceProvider()
};
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"),
});
httpContext.Items[EndpointMiddleware.CorsMiddlewareInvokedKey] = true;
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
// Act & Assert
await middleware.Invoke(httpContext);
// If we got this far, we can sound the everything's OK alarm.
}
[Fact]
public async Task Invoke_WithEndpoint_DoesNotThrowIfUnhandledCorsAttributesWereFound_ButSuppressedViaOptions()
{
// Arrange
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceProvider()
};
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
});
var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, routeOptions);
// Act & Assert
await middleware.Invoke(httpContext);
}
private class ServiceProvider : IServiceProvider
{
public object GetService(Type serviceType)

View File

@ -129,9 +129,6 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
public DefaultCorsPolicyProvider(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions> options) { }
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy> GetPolicyAsync(Microsoft.AspNetCore.Http.HttpContext context, string policyName) { throw null; }
}
public partial interface ICorsMetadata
{
}
public partial interface ICorsPolicyMetadata : Microsoft.AspNetCore.Cors.Infrastructure.ICorsMetadata
{
Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy Policy { get; }

View File

@ -4,3 +4,4 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cors.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp3.0'">
<Compile Include="Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs" />
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" />
</ItemGroup>

View File

@ -137,9 +137,6 @@ namespace Microsoft.AspNetCore.Authorization
[System.Diagnostics.DebuggerStepThroughAttribute]
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationResult> AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object resource, string policyName) { throw null; }
}
public partial interface IAllowAnonymous
{
}
public partial interface IAuthorizationEvaluator
{
Microsoft.AspNetCore.Authorization.AuthorizationResult Evaluate(Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext context);
@ -170,12 +167,6 @@ namespace Microsoft.AspNetCore.Authorization
System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationResult> AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object resource, System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authorization.IAuthorizationRequirement> requirements);
System.Threading.Tasks.Task<Microsoft.AspNetCore.Authorization.AuthorizationResult> AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object resource, string policyName);
}
public partial interface IAuthorizeData
{
string AuthenticationSchemes { get; set; }
string Policy { get; set; }
string Roles { get; set; }
}
}
namespace Microsoft.AspNetCore.Authorization.Infrastructure
{

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>ASP.NET Core authorization classes.
@ -14,6 +14,7 @@ Microsoft.AspNetCore.Authorization.AuthorizeAttribute</Description>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
<Reference Include="Microsoft.Extensions.Logging.Abstractions" />
<Reference Include="Microsoft.Extensions.Options" />
</ItemGroup>

View File

@ -0,0 +1,8 @@
// 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.Runtime.CompilerServices;
using Microsoft.AspNetCore.Authorization;
[assembly: TypeForwardedTo(typeof(IAuthorizeData))]
[assembly: TypeForwardedTo(typeof(IAllowAnonymous))]

View File

@ -26,6 +26,7 @@
<ItemGroup>
<Reference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
<Reference Include="Microsoft.AspNetCore.Cors" />
<Reference Include="Microsoft.AspNetCore.Diagnostics" />
<Reference Include="Microsoft.AspNetCore.Server.IISIntegration" />
<Reference Include="Microsoft.AspNetCore.Server.Kestrel" />

View File

@ -13,7 +13,9 @@ using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Primitives;
@ -47,6 +49,8 @@ namespace FunctionalTests
})
.AddMessagePackProtocol();
services.AddCors();
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
@ -73,17 +77,33 @@ namespace FunctionalTests
{
OnMessageReceived = context =>
{
var signalRTokenHeader = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(signalRTokenHeader) &&
(context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
var endpoint = context.HttpContext.Features.Get<IEndpointFeature>()?.Endpoint;
if (endpoint != null && endpoint.Metadata.GetMetadata<HubMetadata>() != null)
{
context.Token = context.Request.Query["access_token"];
var request = context.HttpContext.Request;
string token = request.Headers["Authorization"];
if (!string.IsNullOrEmpty(token))
{
if (token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = token.Substring("Bearer ".Length).Trim();
}
}
else
{
token = context.Request.Query["access_token"];
}
context.Token = token;
}
return Task.CompletedTask;
}
};
});
services.AddAuthorizationPolicyEvaluator();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
@ -127,8 +147,6 @@ namespace FunctionalTests
return next.Invoke();
});
app.UseRouting();
app.Use((context, next) =>
{
if (context.Request.Path.StartsWithSegments("/redirect"))
@ -148,9 +166,24 @@ namespace FunctionalTests
context.Response.Cookies.Append("testCookie2", "testValue2");
context.Response.Cookies.Append("expiredCookie", "doesntmatter", new CookieOptions() { Expires = DateTimeOffset.Now.AddHours(-1) });
}
await next.Invoke();
});
app.UseRouting();
// Custom CORS to allow any origin + credentials (which isn't allowed by the CORS spec)
// This is for testing purposes only (karma hosts the client on its own server), never do this in production
app.UseCors(policy =>
{
policy.SetIsOriginAllowed(host => host.StartsWith("http://localhost:") || host.StartsWith("http://127.0.0.1:"))
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<TestHub>("/testhub");

View File

@ -1,11 +1,8 @@
// 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 Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.SignalR.Tests
@ -25,11 +22,16 @@ namespace Microsoft.AspNetCore.SignalR.Tests
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie();
services.AddAuthorizationPolicyEvaluator();
}
public void Configure(IApplicationBuilder app)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<UncreatableHub>("/uncreatable");