Skip to content

Commit 0cfe7c5

Browse files
committed
Update CORS middleware to use endpoint metadata
1 parent 87629bb commit 0cfe7c5

File tree

13 files changed

+1471
-1058
lines changed

13 files changed

+1471
-1058
lines changed

src/Middleware/CORS/samples/SampleDestination/Program.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
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

4+
using System;
45
using System.IO;
56
using Microsoft.AspNetCore.Hosting;
67
using Microsoft.Extensions.Logging;
@@ -11,12 +12,30 @@ public class Program
1112
{
1213
public static void Main(string[] args)
1314
{
15+
Type startupType = null;
16+
17+
var startup = Environment.GetEnvironmentVariable("CORS_STARTUP");
18+
switch (startup)
19+
{
20+
case "Startup":
21+
startupType = typeof(Startup);
22+
break;
23+
case "StartupWithoutEndpointRouting":
24+
startupType = typeof(StartupWithoutEndpointRouting);
25+
break;
26+
}
27+
28+
if (startupType == null)
29+
{
30+
throw new InvalidOperationException("Could not resolve the startup type. Unexpected CORS_STARTUP environment variable.");
31+
}
32+
1433
var host = new WebHostBuilder()
1534
.UseKestrel()
1635
.UseUrls("http://+:9000")
1736
.UseContentRoot(Directory.GetCurrentDirectory())
1837
.ConfigureLogging(factory => factory.AddConsole())
19-
.UseStartup<Startup>()
38+
.UseStartup(startupType)
2039
.Build();
2140

2241
host.Run();
Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
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

4-
using System;
54
using System.Net;
65
using System.Text;
76
using System.Threading.Tasks;
@@ -26,66 +25,70 @@ public Startup(ILoggerFactory loggerFactory)
2625

2726
public void ConfigureServices(IServiceCollection services)
2827
{
29-
services.AddCors();
30-
}
31-
32-
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
33-
{
34-
app.Map("/allow-origin", innerBuilder =>
28+
services.AddCors(options =>
3529
{
36-
innerBuilder.UseCors(policy => policy
30+
options.AddPolicy("AllowOrigin", policy => policy
3731
.WithOrigins(DefaultAllowedOrigin)
3832
.AllowAnyMethod()
3933
.AllowAnyHeader());
4034

41-
innerBuilder.UseMiddleware<SampleMiddleware>();
42-
});
43-
44-
app.Map("/allow-header-method", innerBuilder =>
45-
{
46-
innerBuilder.UseCors(policy => policy
35+
options.AddPolicy("AllowHeaderMethod", policy => policy
4736
.WithOrigins(DefaultAllowedOrigin)
4837
.WithHeaders("X-Test", "Content-Type")
4938
.WithMethods("PUT"));
5039

51-
innerBuilder.UseMiddleware<SampleMiddleware>();
52-
});
53-
54-
app.Map("/allow-credentials", innerBuilder =>
55-
{
56-
innerBuilder.UseCors(policy => policy
40+
options.AddPolicy("AllowCredentials", policy => policy
5741
.WithOrigins(DefaultAllowedOrigin)
5842
.AllowAnyHeader()
5943
.WithMethods("GET", "PUT")
6044
.AllowCredentials());
6145

62-
innerBuilder.UseMiddleware<SampleMiddleware>();
63-
});
64-
65-
app.Map("/exposed-header", innerBuilder =>
66-
{
67-
innerBuilder.UseCors(policy => policy
46+
options.AddPolicy("ExposedHeader", policy => policy
6847
.WithOrigins(DefaultAllowedOrigin)
6948
.WithExposedHeaders("X-AllowedHeader", "Content-Length"));
7049

71-
innerBuilder.UseMiddleware<SampleMiddleware>();
72-
});
73-
74-
app.Map("/allow-all", innerBuilder =>
75-
{
76-
innerBuilder.UseCors(policy => policy
50+
options.AddPolicy("AllowAll", policy => policy
7751
.AllowAnyOrigin()
7852
.AllowAnyMethod()
7953
.AllowAnyHeader()
8054
.AllowCredentials());
55+
});
56+
services.AddRouting();
57+
}
8158

82-
innerBuilder.UseMiddleware<SampleMiddleware>();
59+
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
60+
{
61+
app.UseRouting(routing =>
62+
{
63+
routing.Map("/allow-origin", HandleRequest).WithCorsPolicy("AllowOrigin");
64+
routing.Map("/allow-header-method", HandleRequest).WithCorsPolicy("AllowHeaderMethod");
65+
routing.Map("/allow-credentials", HandleRequest).WithCorsPolicy("AllowCredentials");
66+
routing.Map("/exposed-header", HandleRequest).WithCorsPolicy("ExposedHeader");
67+
routing.Map("/allow-all", HandleRequest).WithCorsPolicy("AllowAll");
8368
});
8469

70+
app.UseCors();
71+
72+
app.UseEndpoint();
73+
8574
app.Run(async (context) =>
8675
{
8776
await context.Response.WriteAsync("Hello World!");
8877
});
8978
}
79+
80+
private Task HandleRequest(HttpContext context)
81+
{
82+
var content = Encoding.UTF8.GetBytes("Hello world");
83+
84+
context.Response.Headers["X-AllowedHeader"] = "Test-Value";
85+
context.Response.Headers["X-DisallowedHeader"] = "Test-Value";
86+
87+
context.Response.ContentType = "text/plain; charset=utf-8";
88+
context.Response.ContentLength = content.Length;
89+
context.Response.Body.Write(content, 0, content.Length);
90+
91+
return Task.CompletedTask;
92+
}
9093
}
9194
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 System.Net;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Microsoft.Extensions.Logging;
10+
11+
namespace SampleDestination
12+
{
13+
public class StartupWithoutEndpointRouting
14+
{
15+
private static readonly string DefaultAllowedOrigin = $"http://{Dns.GetHostName()}:9001";
16+
private readonly ILogger<StartupWithoutEndpointRouting> _logger;
17+
18+
public StartupWithoutEndpointRouting(ILoggerFactory loggerFactory)
19+
{
20+
_logger = loggerFactory.CreateLogger<StartupWithoutEndpointRouting>();
21+
_logger.LogInformation($"Setting up CORS middleware to allow clients on {DefaultAllowedOrigin}");
22+
}
23+
24+
public void ConfigureServices(IServiceCollection services)
25+
{
26+
services.AddCors();
27+
}
28+
29+
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
30+
{
31+
app.Map("/allow-origin", innerBuilder =>
32+
{
33+
innerBuilder.UseCors(policy => policy
34+
.WithOrigins(DefaultAllowedOrigin)
35+
.AllowAnyMethod()
36+
.AllowAnyHeader());
37+
38+
innerBuilder.UseMiddleware<SampleMiddleware>();
39+
});
40+
41+
app.Map("/allow-header-method", innerBuilder =>
42+
{
43+
innerBuilder.UseCors(policy => policy
44+
.WithOrigins(DefaultAllowedOrigin)
45+
.WithHeaders("X-Test", "Content-Type")
46+
.WithMethods("PUT"));
47+
48+
innerBuilder.UseMiddleware<SampleMiddleware>();
49+
});
50+
51+
app.Map("/allow-credentials", innerBuilder =>
52+
{
53+
innerBuilder.UseCors(policy => policy
54+
.WithOrigins(DefaultAllowedOrigin)
55+
.AllowAnyHeader()
56+
.WithMethods("GET", "PUT")
57+
.AllowCredentials());
58+
59+
innerBuilder.UseMiddleware<SampleMiddleware>();
60+
});
61+
62+
app.Map("/exposed-header", innerBuilder =>
63+
{
64+
innerBuilder.UseCors(policy => policy
65+
.WithOrigins(DefaultAllowedOrigin)
66+
.WithExposedHeaders("X-AllowedHeader", "Content-Length"));
67+
68+
innerBuilder.UseMiddleware<SampleMiddleware>();
69+
});
70+
71+
app.Map("/allow-all", innerBuilder =>
72+
{
73+
innerBuilder.UseCors(policy => policy
74+
.AllowAnyOrigin()
75+
.AllowAnyMethod()
76+
.AllowAnyHeader()
77+
.AllowCredentials());
78+
79+
innerBuilder.UseMiddleware<SampleMiddleware>();
80+
});
81+
82+
app.Run(async (context) =>
83+
{
84+
await context.Response.WriteAsync("Hello World!");
85+
});
86+
}
87+
}
88+
}

src/Middleware/CORS/samples/SampleOrigin/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
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.IO;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 System;
5+
using Microsoft.AspNetCore.Cors;
6+
using Microsoft.AspNetCore.Routing;
7+
8+
namespace Microsoft.AspNetCore.Builder
9+
{
10+
public static class CorsEndpointConventionBuilderExtensions
11+
{
12+
public static IEndpointConventionBuilder WithCorsPolicy(this IEndpointConventionBuilder builder, string policyName)
13+
{
14+
if (builder == null)
15+
{
16+
throw new ArgumentNullException(nameof(builder));
17+
}
18+
19+
builder.Apply(endpointBuilder =>
20+
{
21+
endpointBuilder.Metadata.Add(new EnableCorsAttribute(policyName));
22+
});
23+
return builder;
24+
}
25+
}
26+
}

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading.Tasks;
66
using Microsoft.AspNetCore.Cors.Internal;
77
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.Http.Endpoints;
89
using Microsoft.Extensions.Logging;
910
using Microsoft.Extensions.Logging.Abstractions;
1011

@@ -124,7 +125,40 @@ public Task Invoke(HttpContext context, ICorsPolicyProvider corsPolicyProvider)
124125

125126
private async Task InvokeCore(HttpContext context, ICorsPolicyProvider corsPolicyProvider)
126127
{
127-
var corsPolicy = _policy ?? await corsPolicyProvider.GetPolicyAsync(context, _corsPolicyName);
128+
// CORS policy resolution rules:
129+
//
130+
// 1. If there is an endpoint with IDisableCorsAttribute then CORS is not run
131+
// 2. If there is an endpoint with IEnableCorsAttribute that has a policy name then
132+
// fetch policy by name, prioritizing it above policy on middleware
133+
// 3. If there is no policy on middleware then use name on middleware
134+
135+
var endpoint = context.GetEndpoint();
136+
137+
// Get the most significant CORS metadata for the endpoint
138+
// For backwards compatibility reasons this is then downcast to Enable/Disable metadata
139+
var corsMetadata = endpoint?.Metadata.GetMetadata<ICorsAttribute>();
140+
if (corsMetadata is IDisableCorsAttribute)
141+
{
142+
await _next(context);
143+
return;
144+
}
145+
146+
var corsPolicy = _policy;
147+
var policyName = _corsPolicyName;
148+
if (corsMetadata is IEnableCorsAttribute enableCorsAttribute &&
149+
enableCorsAttribute.PolicyName != null)
150+
{
151+
// If a policy name has been provided on the endpoint metadata then prioritizing it above the static middleware policy
152+
policyName = enableCorsAttribute.PolicyName;
153+
corsPolicy = null;
154+
}
155+
156+
if (corsPolicy == null)
157+
{
158+
// Resolve policy by name if the local policy is not being used
159+
corsPolicy = await corsPolicyProvider.GetPolicyAsync(context, policyName);
160+
}
161+
128162
if (corsPolicy == null)
129163
{
130164
Logger?.NoCorsPolicyFound();
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
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+
/// A marker interface which can be used to identify CORS metdata.
8+
/// </summary>
9+
public interface ICorsAttribute
10+
{
11+
}
12+
}

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

Lines changed: 2 additions & 2 deletions
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
9+
public interface IDisableCorsAttribute : ICorsAttribute
1010
{
1111
}
12-
}
12+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ 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
9+
public interface IEnableCorsAttribute : ICorsAttribute
1010
{
1111
/// <summary>
1212
/// The name of the policy which needs to be applied.
1313
/// </summary>
1414
string PolicyName { get; set; }
1515
}
16-
}
16+
}

0 commit comments

Comments
 (0)