Skip to content

Commit 73d4704

Browse files
committed
Add metadata for CORS policy
1 parent 11c66ec commit 73d4704

File tree

9 files changed

+151
-10
lines changed

9 files changed

+151
-10
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Cors.Infrastructure;
5+
6+
namespace Microsoft.AspNetCore.Cors
7+
{
8+
public class CorsPolicyMetadata : ICorsPolicyMetadata
9+
{
10+
public CorsPolicyMetadata(CorsPolicy policy)
11+
{
12+
Policy = policy;
13+
}
14+
15+
public CorsPolicyMetadata(string policyName)
16+
{
17+
PolicyName = policyName;
18+
}
19+
20+
public CorsPolicy Policy { get; }
21+
22+
public string PolicyName { get; }
23+
}
24+
}

src/Middleware/CORS/src/Infrastructure/CorsEndpointConventionBuilderExtensions.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System;
55
using Microsoft.AspNetCore.Cors;
6+
using Microsoft.AspNetCore.Cors.Infrastructure;
67
using Microsoft.AspNetCore.Routing;
78

89
namespace Microsoft.AspNetCore.Builder
@@ -27,7 +28,36 @@ public static IEndpointConventionBuilder WithCorsPolicy(this IEndpointConvention
2728

2829
builder.Apply(endpointBuilder =>
2930
{
30-
endpointBuilder.Metadata.Add(new EnableCorsAttribute(policyName));
31+
endpointBuilder.Metadata.Add(new CorsPolicyMetadata(policyName));
32+
});
33+
return builder;
34+
}
35+
36+
/// <summary>
37+
/// Adds the specified CORS policy to the endpoint(s).
38+
/// </summary>
39+
/// <param name="builder">The endpoint convention builder.</param>
40+
/// <param name="configurePolicy">A delegate which can use a policy builder to build a policy.</param>
41+
/// <returns>The original convention builder parameter.</returns>
42+
public static IEndpointConventionBuilder WithCorsPolicy(this IEndpointConventionBuilder builder, Action<CorsPolicyBuilder> configurePolicy)
43+
{
44+
if (builder == null)
45+
{
46+
throw new ArgumentNullException(nameof(builder));
47+
}
48+
49+
if (configurePolicy == null)
50+
{
51+
throw new ArgumentNullException(nameof(configurePolicy));
52+
}
53+
54+
var policyBuilder = new CorsPolicyBuilder();
55+
configurePolicy(policyBuilder);
56+
var policy = policyBuilder.Build();
57+
58+
builder.Apply(endpointBuilder =>
59+
{
60+
endpointBuilder.Metadata.Add(new CorsPolicyMetadata(policy));
3161
});
3262
return builder;
3363
}

src/Middleware/CORS/src/Infrastructure/CorsMiddleware.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,10 @@ private async Task InvokeCore(HttpContext context, ICorsPolicyProvider corsPolic
131131
// CORS policy resolution rules:
132132
//
133133
// 1. If there is an endpoint with IDisableCorsAttribute then CORS is not run
134-
// 2. If there is an endpoint with IEnableCorsAttribute that has a policy name then
134+
// 2. If there is an endpoint with ICorsPolicyMetadata then use its policy and name
135+
// 3. If there is an endpoint with IEnableCorsAttribute that has a policy name then
135136
// fetch policy by name, prioritizing it above policy on middleware
136-
// 3. If there is no policy on middleware then use name on middleware
137+
// 4. If there is no policy on middleware then use name on middleware
137138

138139
// Flag to indicate to other systems, e.g. MVC, that CORS middleware was run for this request
139140
context.Items[CorsMiddlewareInvokedKey] = true;
@@ -142,7 +143,7 @@ private async Task InvokeCore(HttpContext context, ICorsPolicyProvider corsPolic
142143

143144
// Get the most significant CORS metadata for the endpoint
144145
// For backwards compatibility reasons this is then downcast to Enable/Disable metadata
145-
var corsMetadata = endpoint?.Metadata.GetMetadata<ICorsAttribute>();
146+
var corsMetadata = endpoint?.Metadata.GetMetadata<ICorsMetadata>();
146147
if (corsMetadata is IDisableCorsAttribute)
147148
{
148149
await _next(context);
@@ -151,7 +152,12 @@ private async Task InvokeCore(HttpContext context, ICorsPolicyProvider corsPolic
151152

152153
var corsPolicy = _policy;
153154
var policyName = _corsPolicyName;
154-
if (corsMetadata is IEnableCorsAttribute enableCorsAttribute &&
155+
if (corsMetadata is ICorsPolicyMetadata corsPolicyMetadata)
156+
{
157+
policyName = corsPolicyMetadata.PolicyName;
158+
corsPolicy = corsPolicyMetadata.Policy;
159+
}
160+
else if (corsMetadata is IEnableCorsAttribute enableCorsAttribute &&
155161
enableCorsAttribute.PolicyName != null)
156162
{
157163
// If a policy name has been provided on the endpoint metadata then prioritizing it above the static middleware policy

src/Middleware/CORS/src/Infrastructure/ICorsAttribute.cs renamed to src/Middleware/CORS/src/Infrastructure/ICorsMetadata.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
66
/// <summary>
77
/// A marker interface which can be used to identify CORS metdata.
88
/// </summary>
9-
public interface ICorsAttribute
9+
public interface ICorsMetadata
1010
{
1111
}
1212
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
namespace Microsoft.AspNetCore.Cors.Infrastructure
5+
{
6+
/// <summary>
7+
/// An interface which can be used to identify a type which provides metadata needed for enabling CORS support.
8+
/// </summary>
9+
public interface ICorsPolicyMetadata : ICorsMetadata
10+
{
11+
/// <summary>
12+
/// The policy which needs to be applied.
13+
/// </summary>
14+
CorsPolicy Policy { get; }
15+
16+
/// <summary>
17+
/// The name of the policy which needs to be applied.
18+
/// </summary>
19+
string PolicyName { get; }
20+
}
21+
}

src/Middleware/CORS/src/Infrastructure/IDisableCorsAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
66
/// <summary>
77
/// An interface which can be used to identify a type which provides metdata to disable cors for a resource.
88
/// </summary>
9-
public interface IDisableCorsAttribute : ICorsAttribute
9+
public interface IDisableCorsAttribute : ICorsMetadata
1010
{
1111
}
1212
}

src/Middleware/CORS/src/Infrastructure/IEnableCorsAttribute.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
66
/// <summary>
77
/// An interface which can be used to identify a type which provides metadata needed for enabling CORS support.
88
/// </summary>
9-
public interface IEnableCorsAttribute : ICorsAttribute
9+
public interface IEnableCorsAttribute : ICorsMetadata
1010
{
1111
/// <summary>
1212
/// The name of the policy which needs to be applied.

src/Middleware/CORS/test/UnitTests/CorsEndpointConventionBuilderExtensionsTests.cs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Cors.Infrastructure
1313
public class CorsEndpointConventionBuilderExtensionsTests
1414
{
1515
[Fact]
16-
public void WithCorsPolicy_MetadataAdded()
16+
public void WithCorsPolicy_Name_MetadataAdded()
1717
{
1818
// Arrange
1919
var testConventionBuilder = new TestEndpointConventionBuilder();
@@ -28,11 +28,33 @@ public void WithCorsPolicy_MetadataAdded()
2828
addCorsPolicy(endpointModel);
2929
var endpoint = endpointModel.Build();
3030

31-
var metadata = endpoint.Metadata.GetMetadata<IEnableCorsAttribute>();
31+
var metadata = endpoint.Metadata.GetMetadata<ICorsPolicyMetadata>();
3232
Assert.NotNull(metadata);
3333
Assert.Equal("TestPolicyName", metadata.PolicyName);
3434
}
3535

36+
[Fact]
37+
public void WithCorsPolicy_Policy_MetadataAdded()
38+
{
39+
// Arrange
40+
var testConventionBuilder = new TestEndpointConventionBuilder();
41+
42+
// Act
43+
testConventionBuilder.WithCorsPolicy(builder => builder.AllowAnyOrigin());
44+
45+
// Assert
46+
var addCorsPolicy = Assert.Single(testConventionBuilder.Conventions);
47+
48+
var endpointModel = new TestEndpointModel();
49+
addCorsPolicy(endpointModel);
50+
var endpoint = endpointModel.Build();
51+
52+
var metadata = endpoint.Metadata.GetMetadata<ICorsPolicyMetadata>();
53+
Assert.NotNull(metadata);
54+
Assert.NotNull(metadata.Policy);
55+
Assert.True(metadata.Policy.AllowAnyOrigin);
56+
}
57+
3658
private class TestEndpointModel : EndpointModel
3759
{
3860
public override Endpoint Build()

src/Middleware/CORS/test/UnitTests/CorsMiddlewareTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,44 @@ public async Task Invoke_HasEndpointWithEnableMetadata_MiddlewareHasPolicy_RunsC
657657
Times.Once);
658658
}
659659

660+
[Fact]
661+
public async Task Invoke_HasEndpointWithCorsPolicyMetadata_MiddlewareHasPolicy_RunsCorsWithPolicyName()
662+
{
663+
// Arrange
664+
var defaultPolicy = new CorsPolicyBuilder().Build();
665+
var metadataPolicy = new CorsPolicyBuilder().Build();
666+
var mockCorsService = new Mock<ICorsService>();
667+
var mockProvider = new Mock<ICorsPolicyProvider>();
668+
var loggerFactory = NullLoggerFactory.Instance;
669+
mockProvider.Setup(o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()))
670+
.Returns(Task.FromResult<CorsPolicy>(null))
671+
.Verifiable();
672+
mockCorsService.Setup(o => o.EvaluatePolicy(It.IsAny<HttpContext>(), It.IsAny<CorsPolicy>()))
673+
.Returns(new CorsResult())
674+
.Verifiable();
675+
676+
var middleware = new CorsMiddleware(
677+
Mock.Of<RequestDelegate>(),
678+
mockCorsService.Object,
679+
defaultPolicy,
680+
loggerFactory);
681+
682+
var httpContext = new DefaultHttpContext();
683+
httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(new CorsPolicyMetadata(metadataPolicy)), "Test endpoint"));
684+
httpContext.Request.Headers.Add(CorsConstants.Origin, new[] { "http://example.com" });
685+
686+
// Act
687+
await middleware.Invoke(httpContext, mockProvider.Object);
688+
689+
// Assert
690+
mockProvider.Verify(
691+
o => o.GetPolicyAsync(It.IsAny<HttpContext>(), It.IsAny<string>()),
692+
Times.Never);
693+
mockCorsService.Verify(
694+
o => o.EvaluatePolicy(It.IsAny<HttpContext>(), metadataPolicy),
695+
Times.Once);
696+
}
697+
660698
[Fact]
661699
public async Task Invoke_HasEndpointWithEnableMetadataWithNoName_RunsCorsWithStaticPolicy()
662700
{

0 commit comments

Comments
 (0)