diff --git a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs index ed75fdf6e4..858205965d 100644 --- a/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs +++ b/src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs @@ -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 diff --git a/src/Security/Authorization/Core/src/IAllowAnonymous.cs b/src/Http/Http.Abstractions/src/IAllowAnonymous.cs similarity index 79% rename from src/Security/Authorization/Core/src/IAllowAnonymous.cs rename to src/Http/Http.Abstractions/src/IAllowAnonymous.cs index 8531c3daab..1ea1cf5d06 100644 --- a/src/Security/Authorization/Core/src/IAllowAnonymous.cs +++ b/src/Http/Http.Abstractions/src/IAllowAnonymous.cs @@ -4,7 +4,7 @@ namespace Microsoft.AspNetCore.Authorization { /// - /// Marker interface to enable the . + /// Marker interface to allow access to anonymous users. /// public interface IAllowAnonymous { diff --git a/src/Security/Authorization/Core/src/IAuthorizeData.cs b/src/Http/Http.Abstractions/src/IAuthorizeData.cs similarity index 100% rename from src/Security/Authorization/Core/src/IAuthorizeData.cs rename to src/Http/Http.Abstractions/src/IAuthorizeData.cs diff --git a/src/Middleware/CORS/src/Infrastructure/ICorsMetadata.cs b/src/Http/Http.Abstractions/src/ICorsMetadata.cs similarity index 100% rename from src/Middleware/CORS/src/Infrastructure/ICorsMetadata.cs rename to src/Http/Http.Abstractions/src/ICorsMetadata.cs diff --git a/src/Http/Routing/ref/Microsoft.AspNetCore.Routing.netcoreapp3.0.cs b/src/Http/Routing/ref/Microsoft.AspNetCore.Routing.netcoreapp3.0.cs index ba1fc3fc6f..b83aa2efeb 100644 --- a/src/Http/Routing/ref/Microsoft.AspNetCore.Routing.netcoreapp3.0.cs +++ b/src/Http/Routing/ref/Microsoft.AspNetCore.Routing.netcoreapp3.0.cs @@ -322,6 +322,7 @@ namespace Microsoft.AspNetCore.Routing public System.Collections.Generic.ICollection 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 { diff --git a/src/Http/Routing/src/EndpointMiddleware.cs b/src/Http/Routing/src/EndpointMiddleware.cs index 7facd61bb0..00b36cef50 100644 --- a/src/Http/Routing/src/EndpointMiddleware.cs +++ b/src/Http/Routing/src/EndpointMiddleware.cs @@ -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 logger, RequestDelegate next) + public EndpointMiddleware( + ILogger logger, + RequestDelegate next, + IOptions 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()?.Endpoint; if (endpoint?.RequestDelegate != null) { + if (_routeOptions.SuppressCheckForUnhandledSecurityMetadata) + { + // User opted out of this check. + return; + } + + if (endpoint.Metadata.GetMetadata() != null && + !httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey)) + { + ThrowMissingAuthMiddlewareException(endpoint); + } + + if (endpoint.Metadata.GetMetadata() != 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 _executingEndpoint = LoggerMessage.Define( diff --git a/src/Http/Routing/src/RouteOptions.cs b/src/Http/Routing/src/RouteOptions.cs index 5c4de4742c..d83ea590f0 100644 --- a/src/Http/Routing/src/RouteOptions.cs +++ b/src/Http/Routing/src/RouteOptions.cs @@ -28,6 +28,26 @@ namespace Microsoft.AspNetCore.Routing /// public bool AppendTrailingSlash { get; set; } + /// + /// Gets or sets a value that indicates if the check for unhandled security endpoint metadata is suppressed. + /// + /// 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. + /// + /// + /// When , prior to the execution of the endpoint, routing will verify that + /// all known security-specific metadata has been handled. + /// Setting this property to suppresses this check. + /// + /// + /// Defaults to . + /// + /// This check exists as a safeguard against accidental insecure configuration. You may suppress + /// this check if it does not match your application's requirements. + /// + public bool SuppressCheckForUnhandledSecurityMetadata { get; set; } + private IDictionary _constraintTypeMap = GetDefaultConstraintMap(); public IDictionary ConstraintMap diff --git a/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs index 3958222fa4..17832a6b48 100644 --- a/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs +++ b/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs @@ -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 = 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.Instance, next); + var middleware = new EndpointMiddleware(NullLogger.Instance, next, RouteOptions); // Act await middleware.Invoke(httpContext); @@ -49,7 +55,7 @@ namespace Microsoft.AspNetCore.Routing return Task.CompletedTask; }; - var middleware = new EndpointMiddleware(NullLogger.Instance, next); + var middleware = new EndpointMiddleware(NullLogger.Instance, next, RouteOptions); // Act await middleware.Invoke(httpContext); @@ -81,7 +87,7 @@ namespace Microsoft.AspNetCore.Routing return Task.CompletedTask; }; - var middleware = new EndpointMiddleware(NullLogger.Instance, next); + var middleware = new EndpointMiddleware(NullLogger.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(new EndpointSelectorContext() + { + Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), + }); + + var middleware = new EndpointMiddleware(NullLogger.Instance, _ => Task.CompletedTask, RouteOptions); + + // Act & Assert + var ex = await Assert.ThrowsAsync(() => 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(new EndpointSelectorContext() + { + Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), + }); + + httpContext.Items[EndpointMiddleware.AuthorizationMiddlewareInvokedKey] = true; + + var middleware = new EndpointMiddleware(NullLogger.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(new EndpointSelectorContext() + { + Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), + }); + var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true }); + var middleware = new EndpointMiddleware(NullLogger.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(new EndpointSelectorContext() + { + Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), + }); + + var middleware = new EndpointMiddleware(NullLogger.Instance, _ => Task.CompletedTask, RouteOptions); + + // Act & Assert + var ex = await Assert.ThrowsAsync(() => 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(new EndpointSelectorContext() + { + Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), + }); + + httpContext.Items[EndpointMiddleware.CorsMiddlewareInvokedKey] = true; + + var middleware = new EndpointMiddleware(NullLogger.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(new EndpointSelectorContext() + { + Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), + }); + var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true }); + var middleware = new EndpointMiddleware(NullLogger.Instance, _ => Task.CompletedTask, routeOptions); + + // Act & Assert + await middleware.Invoke(httpContext); + } + private class ServiceProvider : IServiceProvider { public object GetService(Type serviceType) diff --git a/src/Middleware/CORS/ref/Microsoft.AspNetCore.Cors.netcoreapp3.0.cs b/src/Middleware/CORS/ref/Microsoft.AspNetCore.Cors.netcoreapp3.0.cs index 1084961a55..4072d5f068 100644 --- a/src/Middleware/CORS/ref/Microsoft.AspNetCore.Cors.netcoreapp3.0.cs +++ b/src/Middleware/CORS/ref/Microsoft.AspNetCore.Cors.netcoreapp3.0.cs @@ -129,9 +129,6 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure public DefaultCorsPolicyProvider(Microsoft.Extensions.Options.IOptions options) { } public System.Threading.Tasks.Task 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; } diff --git a/src/Middleware/CORS/src/Properties/AssemblyInfo.cs b/src/Middleware/CORS/src/Properties/AssemblyInfo.cs index b3d086e2e5..b3ae81a5eb 100644 --- a/src/Middleware/CORS/src/Properties/AssemblyInfo.cs +++ b/src/Middleware/CORS/src/Properties/AssemblyInfo.cs @@ -4,3 +4,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cors.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj index d391139300..a7b1b7a143 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.csproj @@ -5,6 +5,7 @@ + diff --git a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs index 9e59178541..8ed9b6f530 100644 --- a/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs +++ b/src/Security/Authorization/Core/ref/Microsoft.AspNetCore.Authorization.netcoreapp3.0.cs @@ -137,9 +137,6 @@ namespace Microsoft.AspNetCore.Authorization [System.Diagnostics.DebuggerStepThroughAttribute] public System.Threading.Tasks.Task 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 AuthorizeAsync(System.Security.Claims.ClaimsPrincipal user, object resource, System.Collections.Generic.IEnumerable requirements); System.Threading.Tasks.Task 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 { diff --git a/src/Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj b/src/Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj index 01ee46d07d..0fb3159d24 100644 --- a/src/Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj +++ b/src/Security/Authorization/Core/src/Microsoft.AspNetCore.Authorization.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core authorization classes. @@ -14,6 +14,7 @@ Microsoft.AspNetCore.Authorization.AuthorizeAttribute + diff --git a/src/Security/Authorization/Core/src/Properties/AssemblyInfo.cs b/src/Security/Authorization/Core/src/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..6897d6397e --- /dev/null +++ b/src/Security/Authorization/Core/src/Properties/AssemblyInfo.cs @@ -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))] diff --git a/src/SignalR/clients/ts/FunctionalTests/FunctionalTests.csproj b/src/SignalR/clients/ts/FunctionalTests/FunctionalTests.csproj index 6819bd1c36..b6aaa7f555 100644 --- a/src/SignalR/clients/ts/FunctionalTests/FunctionalTests.csproj +++ b/src/SignalR/clients/ts/FunctionalTests/FunctionalTests.csproj @@ -26,6 +26,7 @@ + diff --git a/src/SignalR/clients/ts/FunctionalTests/Startup.cs b/src/SignalR/clients/ts/FunctionalTests/Startup.cs index 41925dc57a..81bd89b2ea 100644 --- a/src/SignalR/clients/ts/FunctionalTests/Startup.cs +++ b/src/SignalR/clients/ts/FunctionalTests/Startup.cs @@ -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()?.Endpoint; + if (endpoint != null && endpoint.Metadata.GetMetadata() != 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"); diff --git a/src/SignalR/server/SignalR/test/Startup.cs b/src/SignalR/server/SignalR/test/Startup.cs index dff6265075..9432148ec9 100644 --- a/src/SignalR/server/SignalR/test/Startup.cs +++ b/src/SignalR/server/SignalR/test/Startup.cs @@ -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("/uncreatable");