Once we’ve got a request authenticated the next thing we might want to consider is authorising those requests as they make access to data. In researching this post I found that I’d actually written about this before but hadn’t been quite as complete as I’d like to be ( and, to be honest, with my 5-minute memory I’d forgotten that I’d written it ) so these posts;
ADO.NET Data Services & Authentication – Basic Auth in IIS 7
ADO.NET Data Services – More Sketchy Thoughts on Access Control
ADO.NET Data Services – Sketchy Thoughts on Controlling Access
just become “backgrounders” for this post with the caveat that they are now a little out of date as Data Services has moved on.
With Data Services we have the possibility to make a bunch of entity sets available to a client and often I use the Northwind database as my example which would mean that if I was just exposing its raw schema I would see something like;
Customers, Orders, Products, Shippers, etc.
and we also have the opportunity to expose service operations which are pre-canned pieces of functionality such as;
GetCustomersByCountry(string country)
which I’m going to ignore for the purposes of the following discussion but will return to later.
In terms of what gets exposed by Data Services – by default it’s nothing and I think that’s a very sensible default.
Unless you take deliberate action none of your data will be exposed and the deliberate action that you need to take to expose your data is to use the interface that’s passed to do you at initialisation time called IDataServiceConfiguration. There’s a method called;
SetEntitySetAccessRule
and we pass this the name of the entity set ( e.g. “Customers” ) and what global level of access we are going to permit. That access is specified by a set of flags called EntitySetRights and they include values;
None, ReadSingle, ReadMultiple, AllRead, WriteAppend, WriteUpdate, WriteDelete, AllWrite, All
This is great because it immediately offers me a way to cut down my attack-surface in that if I have a service where you are not allowed to do anything other than read a single customers record at a time then I can just go ahead and say;
config.SetEntitySetAccessRule(“Customers”, EntitySetRights.ReadSingle);
and I can feel good that I’ve not left a gaping hole for someone to do malicious inserts, updates, deletes or ( as has happened a few times in the UK lately ) download all of the customer details to their laptop and then lose the downlaoded data in the back of a taxi or similar.
Now, whether you’re going to be able to do inserts, updates, deletes also depends on the data source. As far as I know the only data source that will give you this “free” or “out of the box” is the ObjectContext that forms part of the Entity Framework. Other data sources such as a custom source or a LINQ to SQL data source will be read-only unless you supplement them with an implementation of IUpdatable.
With that said, I imagine that a number of services are going to end up with a call to SetEntitySetAccessRule that looks something like this;
config.SetEntitySetAccessRule(“*”, EntitySetRights.All);
Now, this looks pretty scary to me and I’d absolutely say that no-one should do this unless they have to but I figure a number of services will still do it.
Why’s that? Because I imagine that some services will need to do some dynamic checking at runtime to figure out whether authenticated caller Alice who is a member of groups Gp1, Gp2, GpN is allowed to perform [QUERY, INSERT, UPDATE, DELETE] against a particular entity set.
So, when a call comes into our service we’ll need to determine;
- Who’s calling?
- What entity set are they accessing?
- What are they trying to do with it?
in order to figure out whether we allow them to proceed or not.
Now, we already figured out a means for achieving (1) above in the previous post about authentication so we can already use ASP.NET membership ( or other methods ) to figure out what user we have and what groups they belong to.
However, we didn’t do enough to pick up and use ASP.NET roles inside of our service code. We are authenticating with ASP.NET before we get to the WCF code that’s living underneath Data Services and we need to ensure that the principal that ASP.NET sets makes it through to the WCF code because, as far as I know, it won’t happen without a bit of effort so I modified my configuration;
<system.serviceModel> <services> <service name="Service" behaviorConfiguration="myBehaviour"> <endpoint address="" binding="webHttpBinding" contract="System.Data.Services.IRequestHandler"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="myBehaviour"> <serviceAuthorization principalPermissionMode="UseAspNetRoles"> <authorizationPolicies> <add policyType="AuthHelpers.AspPrincipalPolicy, AuthHelpers"/> </authorizationPolicies> </serviceAuthorization> </behavior> </serviceBehaviors> </behaviors> <serviceHostingEnvironment aspNetCompatibilityEnabled="true"> </serviceHostingEnvironment> </system.serviceModel>
and plugged in a new authorization policy whose job is purely to ensure that the “right” principal (i.e. the one produced by ASP.NET authentication) ends up associated with the thread that’s processing the request. As below;
namespace AuthHelpers { public class AspPrincipalPolicy : IAuthorizationPolicy { public bool Evaluate(EvaluationContext evaluationContext, ref object state) { HttpContext context = HttpContext.Current; if (context != null) { evaluationContext.Properties["Identities"] = new List<IIdentity>() { context.User.Identity }; } return true; } public System.IdentityModel.Claims.ClaimSet Issuer { get { return ClaimSet.System; } } public string Id { get { return "Asp Net Auth Policy"; } } } }
Now, the question becomes one of where to put that authorisation code?
We have the possibility to add query and change interceptors to our service. So, we can write something like;
public class Service : DataService<NorthwindEntities> { public static void InitializeService(IDataServiceConfiguration config) { // NB: Not for production. config.SetEntitySetAccessRule("*", EntitySetRights.All); } [QueryInterceptor("Customers")] public Expression<Func<Customers, bool>> MyInterceptor() { return (c => true); } }
and I then have a query interceptor that intercepts any queries for my Customers entity set and that interceptor just so happens to do nothing.
I could then make a static role-based decision as to whether you can query my Customers entity set or not using something as simple as a PrincipalPermission. That is;
[QueryInterceptor("Customers")] [PrincipalPermission(SecurityAction.Demand, Role="Readers")] public Expression<Func<Customers, bool>> MyInterceptor() { return (c => true); }
and now if you’re not in the ASP.NET membership-defined group called “Readers” then you won’t be able to query Customers.
Similarly, for insert/update/delete I could write a change interceptor like this one which makes a more dynamic decision about authorisation. Naturally, I could be using some XML configuration or maybe a plug-in authorisation framework to determine whether to allow access or not but I’m just doing something simple here;
[ChangeInterceptor("Customers")] public void MyChangeInterceptor(object EntitySetObject, UpdateOperations updateAction) { bool allow = false; switch (updateAction) { case UpdateOperations.Add: case UpdateOperations.AddReference: case UpdateOperations.Change: case UpdateOperations.Delete: case UpdateOperations.RemoveReference: allow = Thread.CurrentPrincipal.IsInRole("Writers"); break; default: break; } if (!allow) { throw new UnauthorizedAccessException(); } }
That’s all fine and I could centralise the authorisation code into a single function and call it from many interceptors ( or even plug-in some other authorisation framework ) but the problem I have with this is that every time I want to expose an additional entity set from the service I have to visit the service code and add another Query and Change Interceptor and that feels a little “brittle” to me.
What I really want is a centralised place where I can add one piece of code to the authorisation. In this previous post I decided that one place to put the code would be in a WCF ServiceAuthorizationManager and, in that post, I added one which took a look at the HTTP verb and authorised based on that but, really, just knowing the HTTP verb isn’t enough in that I want to authorise based on;
Entity Set Name
Data Service Operation ( Add, Change, etc. as in the list in the switch statement above )
and that’s not quite the same as taking a look at the HTTP verb and authorising based on that.
So far, I’ve not found any place to centralise this code and hook into the dispatch mechanism that Data Services is using. You might think that it’s better to hook into the underlying WCF infrastructure but I suspect that gets a little tricky given that Data Services can accept both batched and non-batched requests which means you’d have to presumably do quite a lot of work at the WCF level just to figure out what the operation and entity sets are whereas Data Services is already doing this work for both batch and non-batched operations.
So…at the time of writing, I’m still searching for something here. All clues gratefully received 🙂