__Scenario__:
User is using attribute routing and CORs together. He has a global CORS configuration and also a CORS configuration on a particular action.
__Issue__:
The CORS policy decorated on the action isn't being honored and always the global configuration is being considered. This issue does _not_ repro when using conventional routing.
__Reason__:
The root cause of this issue is due to a recent change in AttributeBasedPolicyProviderFactory.cs…specifically this(check highlighted comments) should be “targetRequest"
```
public virtual ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
CorsRequestContext corsRequestContext = request.GetCorsRequestContext();
HttpActionDescriptor actionDescriptor = null;
if (corsRequestContext.IsPreflight)
{
HttpRequestMessage targetRequest = new HttpRequestMessage(new HttpMethod(corsRequestContext.AccessControlRequestMethod), request.RequestUri);
try
{
foreach (var property in request.Properties)
{
// The RouteData and HttpContext from the preflight request properties contain information
// relevant to the preflight request and not the actual request, therefore we need to exclude them.
if (property.Key != HttpPropertyKeys.HttpRouteDataKey &&
property.Key != HttpContextBaseKey)
{
targetRequest.Properties.Add(property.Key, property.Value);
}
}
HttpConfiguration config = request.GetConfiguration(); //*********************HERE*********************************
if (config == null)
{
throw new InvalidOperationException(SRResources.NoConfiguration);
}
IHttpRouteData routeData = config.Routes.GetRouteData(request);//*********************HERE*********************************
if (routeData == null)
{
// No route data found for selecting action with EnableCorsAttribute, thus no ICorsPolicyProvider is returned
// and let the CorsMessageHandler flow the request to the normal Web API pipeline.
return null;
}
actionDescriptor = SelectAction(targetRequest, routeData, config);
}
```
__Setup__:
```
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
config.MapHttpAttributeRoutes();
appBuilder.UseWebApi(config);
}
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
[AcceptVerbs("GET", "OPTIONS", RouteTemplate = "")]
public IEnumerable<string> GetAll()
{
return new string[] { "value1", "value2" };
}
[AcceptVerbs("GET", "OPTIONS", RouteTemplate = "{id}")]
public string GetSingle(int id)
{
return "value";
}
[EnableCors("http://abc.com", "*", "*")]
[AcceptVerbs("POST", "OPTIONS", RouteTemplate = "")]
public string Post([FromBody]string value)
{
return value;
}
```
For the above setup, I am seeing that this request is passing through even though I have specifically mentioned to allow a particular origin(http://abc.com) on the action
```
Request:
Method: OPTIONS, RequestUri: 'http://kcthinkpad:9095/api/values', Version: 1.1, Content: <null>, Headers:
{
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER
}
Response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Access-Control-Allow-Origin: * //*********HERE*********
Access-Control-Allow-Headers: X-PINGOTHER //*********HERE*********
Date: Fri, 09 Aug 2013 14:54:55 GMT
Server: Microsoft-HTTPAPI/2.0
Content-Length: 0
}
----------------------------------
Request:
Method: OPTIONS, RequestUri: 'http://kcthinkpad:9095/api/values', Version: 1.1, Content: <null>, Headers:
{
Origin: http://foo.example
Access-Control-Request-Method: GET
Access-Control-Request-Headers: X-PINGOTHER
}
Response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-PINGOTHER
Date: Fri, 09 Aug 2013 14:54:55 GMT
Server: Microsoft-HTTPAPI/2.0
Content-Length: 0
}
```
User is using attribute routing and CORs together. He has a global CORS configuration and also a CORS configuration on a particular action.
__Issue__:
The CORS policy decorated on the action isn't being honored and always the global configuration is being considered. This issue does _not_ repro when using conventional routing.
__Reason__:
The root cause of this issue is due to a recent change in AttributeBasedPolicyProviderFactory.cs…specifically this(check highlighted comments) should be “targetRequest"
```
public virtual ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
CorsRequestContext corsRequestContext = request.GetCorsRequestContext();
HttpActionDescriptor actionDescriptor = null;
if (corsRequestContext.IsPreflight)
{
HttpRequestMessage targetRequest = new HttpRequestMessage(new HttpMethod(corsRequestContext.AccessControlRequestMethod), request.RequestUri);
try
{
foreach (var property in request.Properties)
{
// The RouteData and HttpContext from the preflight request properties contain information
// relevant to the preflight request and not the actual request, therefore we need to exclude them.
if (property.Key != HttpPropertyKeys.HttpRouteDataKey &&
property.Key != HttpContextBaseKey)
{
targetRequest.Properties.Add(property.Key, property.Value);
}
}
HttpConfiguration config = request.GetConfiguration(); //*********************HERE*********************************
if (config == null)
{
throw new InvalidOperationException(SRResources.NoConfiguration);
}
IHttpRouteData routeData = config.Routes.GetRouteData(request);//*********************HERE*********************************
if (routeData == null)
{
// No route data found for selecting action with EnableCorsAttribute, thus no ICorsPolicyProvider is returned
// and let the CorsMessageHandler flow the request to the normal Web API pipeline.
return null;
}
actionDescriptor = SelectAction(targetRequest, routeData, config);
}
```
__Setup__:
```
public void Configuration(IAppBuilder appBuilder)
{
var config = new HttpConfiguration();
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
config.MapHttpAttributeRoutes();
appBuilder.UseWebApi(config);
}
[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
[AcceptVerbs("GET", "OPTIONS", RouteTemplate = "")]
public IEnumerable<string> GetAll()
{
return new string[] { "value1", "value2" };
}
[AcceptVerbs("GET", "OPTIONS", RouteTemplate = "{id}")]
public string GetSingle(int id)
{
return "value";
}
[EnableCors("http://abc.com", "*", "*")]
[AcceptVerbs("POST", "OPTIONS", RouteTemplate = "")]
public string Post([FromBody]string value)
{
return value;
}
```
For the above setup, I am seeing that this request is passing through even though I have specifically mentioned to allow a particular origin(http://abc.com) on the action
```
Request:
Method: OPTIONS, RequestUri: 'http://kcthinkpad:9095/api/values', Version: 1.1, Content: <null>, Headers:
{
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER
}
Response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Access-Control-Allow-Origin: * //*********HERE*********
Access-Control-Allow-Headers: X-PINGOTHER //*********HERE*********
Date: Fri, 09 Aug 2013 14:54:55 GMT
Server: Microsoft-HTTPAPI/2.0
Content-Length: 0
}
----------------------------------
Request:
Method: OPTIONS, RequestUri: 'http://kcthinkpad:9095/api/values', Version: 1.1, Content: <null>, Headers:
{
Origin: http://foo.example
Access-Control-Request-Method: GET
Access-Control-Request-Headers: X-PINGOTHER
}
Response:
StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.StreamContent, Headers:
{
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: X-PINGOTHER
Date: Fri, 09 Aug 2013 14:54:55 GMT
Server: Microsoft-HTTPAPI/2.0
Content-Length: 0
}
```