Note: This post refers to the CTP available here.
When building frameworks to expose and consume data on the web, access policy and data validation are always one of the first topics discussed during the design process, at conferences, etc. Given this, we thought the topic would be a good one to kick off a series of blog posts about the feature set of the recently announced Dec 07 CTP of the product. If you haven’t seen ADO.NET Data Services / “Project Astoria” before, I suggest checking out http://astoria.mslivelabs.com for an overview and “getting started” style walkthroughs of the product.
Authentication
Before getting into access control and validation, I need to put in a quick note about authentication to set the stage. Data services do not directly implement an authentication scheme. Instead, they rely on the authentication infrastructure of the technology (ex. WCF+ASP.NET, WCF, etc) hosting the data service. If you have an ASP.NET site that uses authentication (either one of the built-in authentication schemes or a custom one that sets the HTTP context principal appropriately), Astoria will simply leverage that mechanism to establish the current identity (principal) for a given request. Ok, now that we know how data services determines who is making a request, its’ time to look at how to implement access control and perform validation based on this information.
Service-wide Access Control
By default all entities, Service Operations and metadata describing any resources in a data service cannot be retrieved. Said another way, by default a data service is completely locked down with no read or write access. This is one of the significant differences from previous (prototype) releases of Astoria. In prior releases, a data service was fully open by default to enable easily creating and evaluating our ideas for what data services on the web might look like. Now that the project has moved out from incubation and into an official product release, we changed such that services are locked down out of the box to align with typical security requirements of production web infrastructures.
One of the first steps a data service developer needs to take is to open up access to the appropriate resources in the data service. One way to do this is to set service wide, access control policy by adding a data service initialization method. The code below shows what a typical data service created over the northwind sample database would look like if only the Customers, Orders and Products sets were exposed.
public class MyDataService : WebDataService<Northwind>
{
public static void InitializeService
(IWebDataServiceConfiguration configuration)
{
// URI ‘/Customers’ entities is enabled for all
//read and write operations
configuration.SetResourceContainerAccessRule
(“Customers”, ResourceContainerRights.All);
// URI ‘/Orders’ is disabled, but ‘/Orders(1)’
// is enabled for read only
configuration.SetResourceContainerAccessRule
(“Orders”, ResourceContainerRights.ReadSingle);
// Can insert and update, but not delete
// Products
configuration.SetResourceContainerAccessRule
(“Products”,
ResourceContainerRights.WriteInsert |
ResourceContainerRights.WriteUpdate);
}
}
The access policies in the snippet above are applied to all requests to the data service and are related to entity sets, not particular URIs. For example, the policy described by the call: ‘SetResourceContainerAccessRule(“Customers”,ResourceContainerRights.All)’ would apply to a request sent to ‘/Customers(‘ALFKI’)’ as well as ’/Orders(1)/Customers’. Basically, the rule is no matter how you address an entity or entity set, the rules in the init method apply equally. All other entities not explicitly enabled in the initialize service method will not be exposed by the data service. For example, the northwind db also includes Employee entities. Since the initialization method does not say anything about Employees, any request to retrieve an employee entity will result in an HTTP 404 (Not Found) response. In addition accessing the endpoint to retrieve the service’s metadata (ex. http://host/service.svc/$metadata) will return a document which only describes those resources where were explicitly made visible.
Per Request Access Control & Validation
Many data services will need to need to run validation logic when entities enter the data service (for inserts, updates or deletes) and/or restrict access to entities on a per request basis. For these scenarios Interceptors are used which enable a data service developer to plug in custom validation or access policy logic into the request/response processing pipeline of a data service. We decided to take the approach of providing infrastructure to build out custom per request access policy instead of defining an access policy model specific to ADO.NET Data Services because we recognize that each application, business, etc have different requirements in this space and that we could add value by providing comprehensive infrastructure elements for one to build custom access policy.
For example, assume you want to implement a policy which enabled customers to only retrieve their orders and not orders placed by other customers. The example below shows a query interceptor that implements this access policy. The important aspect to note about query interceptors is that they accept as a parameter an instance of IQueryable<T> which represents the query the system will push down to the underlying data store to retrieve the requested entity. As shown in the example, this approach enables access policy to be defined using query composition eliminating any added round trips to the data store to retrieve access control information. The example assumes the data service is hosted within a WCF+ASP.NET web application with authentication enabled as the ASP.Net HTTPContext object is used to retrieve the principle of the current request.
using System;
using System.Web;
using System.Collections.Generic;
using System.ServiceModel.Web;
using System.Linq;
using Microsoft.Data.Web;
using NorthwindModel;
namespace TestApplication
{
public class nw : WebDataService<NorthwindModel.NorthwindEntities>
{
public static void InitializeService(
IWebDataServiceConfiguration configuration)
{
configuration.SetResourceContainerAccessRule(“Customers”,
ResourceContainerRights.All);
configuration.SetResourceContainerAccessRule(“Orders”,
ResourceContainerRights.ReadSingle);
configuration.SetResourceContainerAccessRule(“Products”,
ResourceContainerRights.WriteInsert |
ResourceContainerRights.WriteUpdate);
}
[QueryInterceptor(“Orders”)]
public IQueryable<Orders> OnQueryOrders(IQueryable<Orders>
orderQuery)
{
return from o in orderQuery
where o.Customers.ContactName ==
HttpContext.Current.User.Identity.Name
select o;
}
}
}
To hook into update operations (insert, update & delete), change interceptors are used. The example below validates all the Product entities that are created are not discontinued. This would also be the place to implement per request access policy to restrict inserts, updates and/or deletes per request.
[ChangeInterceptor(“Products”)]
public void OnChangeCategories(Products p, ResourceActions action){
if(action == ReceiveEntityOperation.Insert) {
if(p.Discontinued) {
throw new WebDataServiceException(400,
“Cannot add discontinued products”);
}
}
}
To summarize, services created using the ADO.NET Data Services framework are locked down by default and resources must be explicitly exposed. Once exposed, the service developer has a number of hooks at their disposal to implement per request access control and business rule validation.
What do you think of this approach? Is there something we should change/alter/add/etc to better meet your requirements? We look forward to hearing your feedback and continuing our open design process on this blog.
Mike Flasko
Program Manager
This post is part of the transparent design exercise in the Astoria Team. To understand how it works and how your feedback will be used please look at this post.
0 comments