Web API OData Security per Entity

UPDATE: At this point in time I would recommend that you follow the solution posted by vaccano, which is based on input from the OData
team.

What you need to do is to create a new Attribute inheriting from EnableQueryAttribute for OData 4 (or QuerableAttribute depending on which version of Web API\OData you are talking with) and override the ValidateQuery (its the same method as when inheriting from QuerableAttribute) to check for the existence of a suitable SelectExpand attribute.

To setup a new fresh project to test this do the following:

  1. Create a new ASP.Net project with Web API 2
  2. Create your entity framework data context.
  3. Add a new “Web API 2 OData Controller …” controller.
  4. In the WebApiConfigRegister(…) method add the below:

Code:

ODataConventionModelBuilder builder = new ODataConventionModelBuilder();

builder.EntitySet<Customer>("Customers");
builder.EntitySet<Order>("Orders");
builder.EntitySet<OrderDetail>("OrderDetails");

config.Routes.MapODataServiceRoute("odata", "odata", builder.GetEdmModel());

//config.AddODataQueryFilter();
config.AddODataQueryFilter(new SecureAccessAttribute());

In the above, Customer, Order and OrderDetail are my entity framework entities. The config.AddODataQueryFilter(new SecureAccessAttribute()) registers my SecureAccessAttribute for use.

  1. SecureAccessAttribute is implemented as below:

Code:

public class SecureAccessAttribute : EnableQueryAttribute
{
    public override void ValidateQuery(HttpRequestMessage request, ODataQueryOptions queryOptions)
    {
        if(queryOptions.SelectExpand != null
            && queryOptions.SelectExpand.RawExpand != null
            && queryOptions.SelectExpand.RawExpand.Contains("Orders"))
        {
            //Check here if user is allowed to view orders.
            throw new InvalidOperationException();
        }

        base.ValidateQuery(request, queryOptions);
    }
}

Please note that I allow access to the Customers controller, but I limit access to Orders. The only Controller I have implemented is the one below:

public class CustomersController : ODataController
{
    private Entities db = new Entities();

    [SecureAccess(MaxExpansionDepth=2)]
    public IQueryable<Customer> GetCustomers()
    {
        return db.Customers;
    }

    // GET: odata/Customers(5)
    [EnableQuery]
    public SingleResult<Customer> GetCustomer([FromODataUri] int key)
    {
        return SingleResult.Create(db.Customers.Where(customer => customer.Id == key));
    }
}
  1. Apply the attribute in ALL actions that you want to secure. It works exactly as the EnableQueryAttribute. A complete sample (including Nuget packages end everything, making this a 50Mb download) can be found here: http://1drv.ms/1zRmmVj

I just want to also comment a bit on some other solutions:

  1. Leyenda’s solution does not work simply because it is the other way around, but otherwise was super close! The truth is that the builder will look in the entity framework to expand properties and will not hit the Customers controller at all! I do not even have one, and if you remove the security attribute, it will still retrieve the orders just fine if you add the expand command to your query.
  2. Setting the model builder will prohibit access to the entities you removed globally and from everyone, so it is not a good solution.
  3. Feng Zhao’s solution could work, but you would have to manually remove the items you wanted to secure in every query, everywhere, which is not a good solution.

Leave a Comment