-
Notifications
You must be signed in to change notification settings - Fork 9.9k
/
CorsPolicyBuilder.cs
269 lines (237 loc) · 9.28 KB
/
CorsPolicyBuilder.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Linq;
namespace Microsoft.AspNetCore.Cors.Infrastructure;
/// <summary>
/// Exposes methods to build a policy.
/// </summary>
public class CorsPolicyBuilder
{
private readonly CorsPolicy _policy = new CorsPolicy();
/// <summary>
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
/// </summary>
/// <param name="origins">list of origins which can be added.</param>
/// <remarks> <see cref="WithOrigins(string[])"/> for details on normalizing the origin value.</remarks>
public CorsPolicyBuilder(params string[] origins)
{
WithOrigins(origins);
}
/// <summary>
/// Creates a new instance of the <see cref="CorsPolicyBuilder"/>.
/// </summary>
/// <param name="policy">The policy which will be used to intialize the builder.</param>
public CorsPolicyBuilder(CorsPolicy policy)
{
Combine(policy);
}
/// <summary>
/// Adds the specified <paramref name="origins"/> to the policy.
/// </summary>
/// <param name="origins">The origins that are allowed.</param>
/// <returns>The current policy builder.</returns>
/// <remarks>
/// This method normalizes the origin value prior to adding it to <see cref="CorsPolicy.Origins"/> to match
/// the normalization performed by the browser on the value sent in the <c>ORIGIN</c> header.
/// <list type="bullet">
/// <item>
/// <description>If the specified origin has an internationalized domain name (IDN), the punycoded value is used. If the origin
/// specifies a default port (e.g. 443 for HTTPS or 80 for HTTP), this will be dropped as part of normalization.
/// Finally, the scheme and punycoded host name are culture invariant lower cased before being added to the <see cref="CorsPolicy.Origins"/>
/// collection.</description>
/// </item>
/// <item>
/// <description>For all other origins, normalization involves performing a culture invariant lower casing of the host name.</description>
/// </item>
/// </list>
/// </remarks>
public CorsPolicyBuilder WithOrigins(params string[] origins)
{
ArgumentNullException.ThrowIfNull(origins);
foreach (var origin in origins)
{
var normalizedOrigin = GetNormalizedOrigin(origin);
_policy.Origins.Add(normalizedOrigin);
}
return this;
}
internal static string GetNormalizedOrigin(string origin)
{
ArgumentNullException.ThrowIfNull(origin);
if (Uri.TryCreate(origin, UriKind.Absolute, out var uri) &&
(uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) &&
!string.Equals(uri.IdnHost, uri.Host, StringComparison.Ordinal))
{
var builder = new UriBuilder(uri.Scheme.ToLowerInvariant(), uri.IdnHost.ToLowerInvariant());
if (!uri.IsDefaultPort)
{
// Uri does not have a way to differentiate between a port value inferred by default (e.g. Port = 80 for http://www.example.com) and
// a default port value that is specified (e.g. Port = 80 for http://www.example.com:80). Although the HTTP or FETCH spec does not say
// anything about including the default port as part of the Origin header, at the time of writing, browsers drop "default" port when navigating
// and when sending the Origin header. All this goes to say, it appears OK to drop an explicitly specified port,
// if it is the default port when working with an IDN host.
builder.Port = uri.Port;
}
return builder.Uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
}
return origin.ToLowerInvariant();
}
/// <summary>
/// Adds the specified <paramref name="headers"/> to the policy.
/// </summary>
/// <param name="headers">The headers which need to be allowed in the request.</param>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder WithHeaders(params string[] headers)
{
foreach (var req in headers)
{
_policy.Headers.Add(req);
}
return this;
}
/// <summary>
/// Adds the specified <paramref name="exposedHeaders"/> to the policy.
/// </summary>
/// <param name="exposedHeaders">The headers which need to be exposed to the client.</param>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder WithExposedHeaders(params string[] exposedHeaders)
{
foreach (var req in exposedHeaders)
{
_policy.ExposedHeaders.Add(req);
}
return this;
}
/// <summary>
/// Adds the specified <paramref name="methods"/> to the policy.
/// </summary>
/// <param name="methods">The methods which need to be added to the policy.</param>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder WithMethods(params string[] methods)
{
foreach (var req in methods)
{
_policy.Methods.Add(req);
}
return this;
}
/// <summary>
/// Sets the policy to allow credentials.
/// </summary>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder AllowCredentials()
{
_policy.SupportsCredentials = true;
return this;
}
/// <summary>
/// Sets the policy to not allow credentials.
/// </summary>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder DisallowCredentials()
{
_policy.SupportsCredentials = false;
return this;
}
/// <summary>
/// Ensures that the policy allows any origin.
/// </summary>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder AllowAnyOrigin()
{
_policy.Origins.Clear();
_policy.Origins.Add(CorsConstants.AnyOrigin);
return this;
}
/// <summary>
/// Ensures that the policy allows any method.
/// </summary>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder AllowAnyMethod()
{
_policy.Methods.Clear();
_policy.Methods.Add("*");
return this;
}
/// <summary>
/// Ensures that the policy allows any header.
/// </summary>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder AllowAnyHeader()
{
_policy.Headers.Clear();
_policy.Headers.Add("*");
return this;
}
/// <summary>
/// Sets the preflightMaxAge for the underlying policy.
/// </summary>
/// <param name="preflightMaxAge">A positive <see cref="TimeSpan"/> indicating the time a preflight
/// request can be cached.</param>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder SetPreflightMaxAge(TimeSpan preflightMaxAge)
{
_policy.PreflightMaxAge = preflightMaxAge;
return this;
}
/// <summary>
/// Sets the specified <paramref name="isOriginAllowed"/> for the underlying policy.
/// </summary>
/// <param name="isOriginAllowed">The function used by the policy to evaluate if an origin is allowed.</param>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder SetIsOriginAllowed(Func<string, bool> isOriginAllowed)
{
_policy.IsOriginAllowed = isOriginAllowed;
return this;
}
/// <summary>
/// Sets the <see cref="CorsPolicy.IsOriginAllowed"/> property of the policy to be a function
/// that allows origins to match a configured wildcarded domain when evaluating if the
/// origin is allowed.
/// </summary>
/// <returns>The current policy builder.</returns>
public CorsPolicyBuilder SetIsOriginAllowedToAllowWildcardSubdomains()
{
_policy.IsOriginAllowed = _policy.IsOriginAnAllowedSubdomain;
return this;
}
/// <summary>
/// Builds a new <see cref="CorsPolicy"/> using the entries added.
/// </summary>
/// <returns>The constructed <see cref="CorsPolicy"/>.</returns>
public CorsPolicy Build()
{
if (_policy.AllowAnyOrigin && _policy.SupportsCredentials)
{
throw new InvalidOperationException(Resources.InsecureConfiguration);
}
return _policy;
}
/// <summary>
/// Combines the given <paramref name="policy"/> to the existing properties in the builder.
/// </summary>
/// <param name="policy">The policy which needs to be combined.</param>
/// <returns>The current policy builder.</returns>
private CorsPolicyBuilder Combine(CorsPolicy policy)
{
ArgumentNullException.ThrowIfNull(policy);
WithOrigins(policy.Origins.ToArray());
WithHeaders(policy.Headers.ToArray());
WithExposedHeaders(policy.ExposedHeaders.ToArray());
WithMethods(policy.Methods.ToArray());
SetIsOriginAllowed(policy.IsOriginAllowed);
if (policy.PreflightMaxAge.HasValue)
{
SetPreflightMaxAge(policy.PreflightMaxAge.Value);
}
if (policy.SupportsCredentials)
{
AllowCredentials();
}
else
{
DisallowCredentials();
}
return this;
}
}