Skip to content

Commit ae34697

Browse files
pranavkmnatemcmaster
authored andcommitted
Fix #9041 - Complain if auth hasn't been set up correctly (#9181)
1 parent 8af6e9e commit ae34697

17 files changed

+296
-41
lines changed

src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp3.0.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
namespace Microsoft.AspNetCore.Authorization
5+
{
6+
public partial interface IAllowAnonymous
7+
{
8+
}
9+
public partial interface IAuthorizeData
10+
{
11+
string AuthenticationSchemes { get; set; }
12+
string Policy { get; set; }
13+
string Roles { get; set; }
14+
}
15+
}
416
namespace Microsoft.AspNetCore.Builder
517
{
618
public abstract partial class EndpointBuilder
@@ -87,6 +99,12 @@ public UsePathBaseMiddleware(Microsoft.AspNetCore.Http.RequestDelegate next, Mic
8799
public System.Threading.Tasks.Task Invoke(Microsoft.AspNetCore.Http.HttpContext context) { throw null; }
88100
}
89101
}
102+
namespace Microsoft.AspNetCore.Cors.Infrastructure
103+
{
104+
public partial interface ICorsMetadata
105+
{
106+
}
107+
}
90108
namespace Microsoft.AspNetCore.Http
91109
{
92110
public abstract partial class ConnectionInfo

src/Security/Authorization/Core/src/IAllowAnonymous.cs renamed to src/Http/Http.Abstractions/src/IAllowAnonymous.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace Microsoft.AspNetCore.Authorization
55
{
66
/// <summary>
7-
/// Marker interface to enable the <see cref="AllowAnonymousAttribute"/>.
7+
/// Marker interface to allow access to anonymous users.
88
/// </summary>
99
public interface IAllowAnonymous
1010
{
File renamed without changes.
File renamed without changes.

src/Http/Routing/ref/Microsoft.AspNetCore.Routing.netcoreapp3.0.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ public RouteOptions() { }
322322
public System.Collections.Generic.ICollection<Microsoft.AspNetCore.Routing.EndpointDataSource> EndpointDataSources { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
323323
public bool LowercaseQueryStrings { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
324324
public bool LowercaseUrls { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
325+
public bool SuppressCheckForUnhandledSecurityMetadata { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
325326
}
326327
public partial class RouteValueEqualityComparer : System.Collections.Generic.IEqualityComparer<object>
327328
{

src/Http/Routing/src/EndpointMiddleware.cs

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,59 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
55
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Authorization;
7+
using Microsoft.AspNetCore.Cors.Infrastructure;
68
using Microsoft.AspNetCore.Http;
79
using Microsoft.AspNetCore.Http.Features;
810
using Microsoft.Extensions.Logging;
11+
using Microsoft.Extensions.Options;
912

1013
namespace Microsoft.AspNetCore.Routing
1114
{
1215
internal sealed class EndpointMiddleware
1316
{
17+
internal const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareInvoked";
18+
internal const string CorsMiddlewareInvokedKey = "__CorsMiddlewareInvoked";
19+
1420
private readonly ILogger _logger;
1521
private readonly RequestDelegate _next;
22+
private readonly RouteOptions _routeOptions;
1623

17-
public EndpointMiddleware(ILogger<EndpointMiddleware> logger, RequestDelegate next)
24+
public EndpointMiddleware(
25+
ILogger<EndpointMiddleware> logger,
26+
RequestDelegate next,
27+
IOptions<RouteOptions> routeOptions)
1828
{
19-
if (logger == null)
20-
{
21-
throw new ArgumentNullException(nameof(logger));
22-
}
23-
24-
if (next == null)
25-
{
26-
throw new ArgumentNullException(nameof(next));
27-
}
28-
29-
_logger = logger;
30-
_next = next;
29+
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
30+
_next = next ?? throw new ArgumentNullException(nameof(next));
31+
_routeOptions = routeOptions?.Value ?? throw new ArgumentNullException(nameof(routeOptions));
3132
}
3233

3334
public async Task Invoke(HttpContext httpContext)
3435
{
3536
var endpoint = httpContext.Features.Get<IEndpointFeature>()?.Endpoint;
3637
if (endpoint?.RequestDelegate != null)
3738
{
39+
if (_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
40+
{
41+
// User opted out of this check.
42+
return;
43+
}
44+
45+
if (endpoint.Metadata.GetMetadata<IAuthorizeData>() != null &&
46+
!httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
47+
{
48+
ThrowMissingAuthMiddlewareException(endpoint);
49+
}
50+
51+
if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != null &&
52+
!httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
53+
{
54+
ThrowMissingCorsMiddlewareException(endpoint);
55+
}
56+
3857
Log.ExecutingEndpoint(_logger, endpoint);
3958

4059
try
@@ -52,6 +71,22 @@ public async Task Invoke(HttpContext httpContext)
5271
await _next(httpContext);
5372
}
5473

74+
private static void ThrowMissingAuthMiddlewareException(Endpoint endpoint)
75+
{
76+
throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains authorization metadata, " +
77+
"but a middleware was not found that supports authorization." +
78+
Environment.NewLine +
79+
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code.");
80+
}
81+
82+
private static void ThrowMissingCorsMiddlewareException(Endpoint endpoint)
83+
{
84+
throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains CORS metadata, " +
85+
"but a middleware was not found that supports CORS." +
86+
Environment.NewLine +
87+
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code.");
88+
}
89+
5590
private static class Log
5691
{
5792
private static readonly Action<ILogger, string, Exception> _executingEndpoint = LoggerMessage.Define<string>(

src/Http/Routing/src/RouteOptions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,26 @@ public class RouteOptions
2828
/// </summary>
2929
public bool AppendTrailingSlash { get; set; }
3030

31+
/// <summary>
32+
/// Gets or sets a value that indicates if the check for unhandled security endpoint metadata is suppressed.
33+
/// <para>
34+
/// Endpoints can be associated with metadata such as authorization, or CORS, that needs to be
35+
/// handled by a specific middleware to be actionable. If the middleware is not configured, such
36+
/// metadata will go unhandled.
37+
/// </para>
38+
/// <para>
39+
/// When <see langword="false"/>, prior to the execution of the endpoint, routing will verify that
40+
/// all known security-specific metadata has been handled.
41+
/// Setting this property to <see langword="true"/> suppresses this check.
42+
/// </para>
43+
/// </summary>
44+
/// <value>Defaults to <see langword="false"/>.</value>
45+
/// <remarks>
46+
/// This check exists as a safeguard against accidental insecure configuration. You may suppress
47+
/// this check if it does not match your application's requirements.
48+
/// </remarks>
49+
public bool SuppressCheckForUnhandledSecurityMetadata { get; set; }
50+
3151
private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
3252

3353
public IDictionary<string, Type> ConstraintMap

src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs

Lines changed: 150 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
1+
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
55
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Authorization;
7+
using Microsoft.AspNetCore.Cors.Infrastructure;
68
using Microsoft.AspNetCore.Http;
79
using Microsoft.AspNetCore.Http.Features;
810
using Microsoft.Extensions.Logging.Abstractions;
11+
using Microsoft.Extensions.Options;
12+
using Moq;
913
using Xunit;
1014

1115
namespace Microsoft.AspNetCore.Routing
1216
{
1317
public class EndpointMiddlewareTest
1418
{
19+
private readonly IOptions<RouteOptions> RouteOptions = Options.Create(new RouteOptions());
20+
1521
[Fact]
1622
public async Task Invoke_NoFeature_NoOps()
1723
{
@@ -24,7 +30,7 @@ public async Task Invoke_NoFeature_NoOps()
2430
return Task.CompletedTask;
2531
};
2632

27-
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next);
33+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
2834

2935
// Act
3036
await middleware.Invoke(httpContext);
@@ -49,7 +55,7 @@ public async Task Invoke_NoEndpoint_NoOps()
4955
return Task.CompletedTask;
5056
};
5157

52-
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next);
58+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
5359

5460
// Act
5561
await middleware.Invoke(httpContext);
@@ -81,7 +87,7 @@ public async Task Invoke_WithEndpoint_InvokesDelegate()
8187
return Task.CompletedTask;
8288
};
8389

84-
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next);
90+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, next, RouteOptions);
8591

8692
// Act
8793
await middleware.Invoke(httpContext);
@@ -90,6 +96,146 @@ public async Task Invoke_WithEndpoint_InvokesDelegate()
9096
Assert.True(invoked);
9197
}
9298

99+
[Fact]
100+
public async Task Invoke_WithEndpoint_ThrowsIfAuthAttributesWereFound_ButAuthMiddlewareNotInvoked()
101+
{
102+
// Arrange
103+
var expected = "Endpoint Test contains authorization metadata, but a middleware was not found that supports authorization." +
104+
Environment.NewLine +
105+
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code.";
106+
var httpContext = new DefaultHttpContext
107+
{
108+
RequestServices = new ServiceProvider()
109+
};
110+
111+
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
112+
{
113+
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
114+
});
115+
116+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
117+
118+
// Act & Assert
119+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => middleware.Invoke(httpContext));
120+
121+
// Assert
122+
Assert.Equal(expected, ex.Message);
123+
}
124+
125+
[Fact]
126+
public async Task Invoke_WithEndpoint_WorksIfAuthAttributesWereFound_AndAuthMiddlewareInvoked()
127+
{
128+
// Arrange
129+
var httpContext = new DefaultHttpContext
130+
{
131+
RequestServices = new ServiceProvider()
132+
};
133+
134+
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
135+
{
136+
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
137+
});
138+
139+
httpContext.Items[EndpointMiddleware.AuthorizationMiddlewareInvokedKey] = true;
140+
141+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
142+
143+
// Act & Assert
144+
await middleware.Invoke(httpContext);
145+
146+
// If we got this far, we can sound the everything's OK alarm.
147+
}
148+
149+
[Fact]
150+
public async Task Invoke_WithEndpoint_DoesNotThrowIfUnhandledAuthAttributesWereFound_ButSuppressedViaOptions()
151+
{
152+
// Arrange
153+
var httpContext = new DefaultHttpContext
154+
{
155+
RequestServices = new ServiceProvider()
156+
};
157+
158+
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
159+
{
160+
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
161+
});
162+
var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
163+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, routeOptions);
164+
165+
// Act & Assert
166+
await middleware.Invoke(httpContext);
167+
}
168+
169+
[Fact]
170+
public async Task Invoke_WithEndpoint_ThrowsIfCorsMetadataWasFound_ButCorsMiddlewareNotInvoked()
171+
{
172+
// Arrange
173+
var expected = "Endpoint Test contains CORS metadata, but a middleware was not found that supports CORS." +
174+
Environment.NewLine +
175+
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code.";
176+
var httpContext = new DefaultHttpContext
177+
{
178+
RequestServices = new ServiceProvider()
179+
};
180+
181+
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
182+
{
183+
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"),
184+
});
185+
186+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
187+
188+
// Act & Assert
189+
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => middleware.Invoke(httpContext));
190+
191+
// Assert
192+
Assert.Equal(expected, ex.Message);
193+
}
194+
195+
[Fact]
196+
public async Task Invoke_WithEndpoint_WorksIfCorsMetadataWasFound_AndCorsMiddlewareInvoked()
197+
{
198+
// Arrange
199+
var httpContext = new DefaultHttpContext
200+
{
201+
RequestServices = new ServiceProvider()
202+
};
203+
204+
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
205+
{
206+
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"),
207+
});
208+
209+
httpContext.Items[EndpointMiddleware.CorsMiddlewareInvokedKey] = true;
210+
211+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
212+
213+
// Act & Assert
214+
await middleware.Invoke(httpContext);
215+
216+
// If we got this far, we can sound the everything's OK alarm.
217+
}
218+
219+
[Fact]
220+
public async Task Invoke_WithEndpoint_DoesNotThrowIfUnhandledCorsAttributesWereFound_ButSuppressedViaOptions()
221+
{
222+
// Arrange
223+
var httpContext = new DefaultHttpContext
224+
{
225+
RequestServices = new ServiceProvider()
226+
};
227+
228+
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
229+
{
230+
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
231+
});
232+
var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
233+
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, routeOptions);
234+
235+
// Act & Assert
236+
await middleware.Invoke(httpContext);
237+
}
238+
93239
private class ServiceProvider : IServiceProvider
94240
{
95241
public object GetService(Type serviceType)

src/Middleware/CORS/ref/Microsoft.AspNetCore.Cors.netcoreapp3.0.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ public partial class DefaultCorsPolicyProvider : Microsoft.AspNetCore.Cors.Infra
129129
public DefaultCorsPolicyProvider(Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Cors.Infrastructure.CorsOptions> options) { }
130130
public System.Threading.Tasks.Task<Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy> GetPolicyAsync(Microsoft.AspNetCore.Http.HttpContext context, string policyName) { throw null; }
131131
}
132-
public partial interface ICorsMetadata
133-
{
134-
}
135132
public partial interface ICorsPolicyMetadata : Microsoft.AspNetCore.Cors.Infrastructure.ICorsMetadata
136133
{
137134
Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicy Policy { get; }

src/Middleware/CORS/src/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
using System.Runtime.CompilerServices;
55

66
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Cors.Test,PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
7+

0 commit comments

Comments
 (0)