Skip to content
This repository was archived by the owner on Nov 25, 2019. It is now read-only.

Commit bfa28d1

Browse files
author
youssefm
committed
[OData] Refactor QueryableAttribute's ValidateQuery method to optionally validate the ODataQueryOptions as well
1 parent 235544c commit bfa28d1

File tree

2 files changed

+48
-28
lines changed

2 files changed

+48
-28
lines changed

src/System.Web.Http.OData/QueryableAttribute.cs

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,6 @@ public override void OnActionExecuted(HttpActionExecutedContext actionExecutedCo
158158
if (responseContent.Value != null && request.RequestUri != null &&
159159
(!String.IsNullOrWhiteSpace(request.RequestUri.Query) || _resultLimit.HasValue))
160160
{
161-
ValidateQuery(request);
162-
163161
try
164162
{
165163
IEnumerable query = responseContent.Value as IEnumerable;
@@ -232,16 +230,7 @@ private IQueryable ExecuteQuery(IEnumerable query, HttpRequestMessage request, H
232230

233231
ODataQueryContext queryContext = CreateQueryContext(entityClrType, configuration, actionDescriptor);
234232
ODataQueryOptions queryOptions = new ODataQueryOptions(queryContext, request);
235-
236-
// Filter and OrderBy require entity sets. Top and Skip may accept primitives.
237-
if (queryContext.IsPrimitiveClrType && (queryOptions.Filter != null || queryOptions.OrderBy != null))
238-
{
239-
// An attempt to use a query option not allowed for primitive types
240-
// generates a BadRequest with a general message that avoids information disclosure.
241-
throw new HttpResponseException(request.CreateErrorResponse(
242-
HttpStatusCode.BadRequest,
243-
SRResources.OnlySkipAndTopSupported));
244-
}
233+
ValidateQuery(request, queryOptions);
245234

246235
// apply the query
247236
IQueryable queryable = query as IQueryable;
@@ -287,22 +276,27 @@ private static ODataQueryContext CreateQueryContext(Type entityClrType, HttpConf
287276
}
288277

289278
/// <summary>
290-
/// Validates that the OData query parameters of the incoming request are supported.
279+
/// Validates that the OData query of the incoming request is supported.
291280
/// </summary>
281+
/// <param name="request">The incoming request</param>
282+
/// <param name="queryOptions">The <see cref="ODataQueryOptions"/> instance constructed based on the incoming request.</param>
292283
/// <remarks>
293-
/// Override this method to add support for new OData query parameters
294-
/// Throw <see cref="HttpResponseException"/> for unsupported query parameters.
284+
/// Override this method to perform additional validation of the query. By default, the implementation
285+
/// throws an exception if the query contains unsupported query parameters.
295286
/// </remarks>
296-
/// <param name="request">The incoming request</param>
297-
/// <exception cref="HttpResponseException">The request contains unsupported OData query parameters.</exception>
298287
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Response disposed after being sent.")]
299-
public virtual void ValidateQuery(HttpRequestMessage request)
288+
public virtual void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
300289
{
301290
if (request == null)
302291
{
303292
throw Error.ArgumentNull("request");
304293
}
305294

295+
if (queryOptions == null)
296+
{
297+
throw Error.ArgumentNull("queryOptions");
298+
}
299+
306300
IEnumerable<KeyValuePair<string, string>> queryParameters = request.GetQueryNameValuePairs();
307301
foreach (KeyValuePair<string, string> kvp in queryParameters)
308302
{
@@ -314,6 +308,16 @@ public virtual void ValidateQuery(HttpRequestMessage request)
314308
Error.Format(SRResources.QueryParameterNotSupported, kvp.Key)));
315309
}
316310
}
311+
312+
// Filter and OrderBy require entity sets. Top and Skip may accept primitives.
313+
if (queryOptions.Context.IsPrimitiveClrType && (queryOptions.Filter != null || queryOptions.OrderBy != null))
314+
{
315+
// An attempt to use a query option not allowed for primitive types
316+
// generates a BadRequest with a general message that avoids information disclosure.
317+
throw new HttpResponseException(request.CreateErrorResponse(
318+
HttpStatusCode.BadRequest,
319+
SRResources.OnlySkipAndTopSupported));
320+
}
317321
}
318322
}
319323
}

test/System.Web.Http.OData.Test/QueryableAttributeTests.cs

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Web.Http.Controllers;
1212
using System.Web.Http.Filters;
1313
using System.Web.Http.Hosting;
14+
using System.Web.Http.OData.Builder;
1415
using System.Web.Http.OData.Query;
1516
using System.Web.Http.OData.Query.Controllers;
1617
using System.Web.Http.OData.TestCommon.Models;
@@ -357,9 +358,21 @@ public void ValidateQuery_Throws_With_Null_Request()
357358
{
358359
// Arrange
359360
QueryableAttribute attribute = new QueryableAttribute();
361+
var model = new ODataModelBuilder().Add_Customer_EntityType().Add_Customers_EntitySet().GetEdmModel();
362+
var options = new ODataQueryOptions(new ODataQueryContext(model, typeof(System.Web.Http.OData.Builder.TestModels.Customer), "Customers"), new HttpRequestMessage());
360363

361364
// Act & Assert
362-
Assert.ThrowsArgumentNull(() => attribute.ValidateQuery(null), "request");
365+
Assert.ThrowsArgumentNull(() => attribute.ValidateQuery(null, options), "request");
366+
}
367+
368+
[Fact]
369+
public void ValidateQuery_Throws_WithNullQueryOptions()
370+
{
371+
// Arrange
372+
QueryableAttribute attribute = new QueryableAttribute();
373+
374+
// Act & Assert
375+
Assert.ThrowsArgumentNull(() => attribute.ValidateQuery(new HttpRequestMessage(), null), "queryOptions");
363376
}
364377

365378
[Theory]
@@ -368,10 +381,12 @@ public void ValidateQuery_Accepts_All_Supported_QueryNames(string queryName)
368381
{
369382
// Arrange
370383
QueryableAttribute attribute = new QueryableAttribute();
371-
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/?" + queryName);
384+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/?" + queryName + "=x");
385+
var model = new ODataModelBuilder().Add_Customer_EntityType().Add_Customers_EntitySet().GetEdmModel();
386+
var options = new ODataQueryOptions(new ODataQueryContext(model, typeof(System.Web.Http.OData.Builder.TestModels.Customer), "Customers"), request);
372387

373388
// Act & Assert
374-
attribute.ValidateQuery(request);
389+
attribute.ValidateQuery(request, options);
375390
}
376391

377392
[Theory]
@@ -381,10 +396,12 @@ public void ValidateQuery_Sends_BadRequest_For_Unsupported_QueryNames(string que
381396
// Arrange
382397
QueryableAttribute attribute = new QueryableAttribute();
383398
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/?" + queryName);
399+
var model = new ODataModelBuilder().Add_Customer_EntityType().Add_Customers_EntitySet().GetEdmModel();
400+
var options = new ODataQueryOptions(new ODataQueryContext(model, typeof(System.Web.Http.OData.Builder.TestModels.Customer), "Customers"), request);
384401

385402
// Act & Assert
386403
HttpResponseException responseException = Assert.Throws<HttpResponseException>(
387-
() => attribute.ValidateQuery(request));
404+
() => attribute.ValidateQuery(request, options));
388405

389406
Assert.Equal(HttpStatusCode.BadRequest, responseException.Response.StatusCode);
390407
}
@@ -395,10 +412,12 @@ public void ValidateQuery_Sends_BadRequest_For_Unrecognized_QueryNames()
395412
// Arrange
396413
QueryableAttribute attribute = new QueryableAttribute();
397414
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/?$xxx");
415+
var model = new ODataModelBuilder().Add_Customer_EntityType().Add_Customers_EntitySet().GetEdmModel();
416+
var options = new ODataQueryOptions(new ODataQueryContext(model, typeof(System.Web.Http.OData.Builder.TestModels.Customer), "Customers"), request);
398417

399418
// Act & Assert
400419
HttpResponseException responseException = Assert.Throws<HttpResponseException>(
401-
() => attribute.ValidateQuery(request));
420+
() => attribute.ValidateQuery(request, options));
402421

403422
Assert.Equal(HttpStatusCode.BadRequest, responseException.Response.StatusCode);
404423
}
@@ -408,13 +427,10 @@ public void ValidateQuery_Can_Override_Base()
408427
{
409428
// Arrange
410429
Mock<QueryableAttribute> mockAttribute = new Mock<QueryableAttribute>();
411-
mockAttribute.Setup(m => m.ValidateQuery(It.IsAny<HttpRequestMessage>())).Callback(() => { }).Verifiable();
412-
413-
QueryableAttribute attribute = new QueryableAttribute();
414-
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/?$xxx");
430+
mockAttribute.Setup(m => m.ValidateQuery(It.IsAny<HttpRequestMessage>(), It.IsAny<ODataQueryOptions>())).Callback(() => { }).Verifiable();
415431

416432
// Act & Assert
417-
mockAttribute.Object.ValidateQuery(null);
433+
mockAttribute.Object.ValidateQuery(null, null);
418434
mockAttribute.Verify();
419435
}
420436

0 commit comments

Comments
 (0)