Securing entity properties with WCF RIA Services

Although WCF RIA Services provides a pretty good way to support entity-level security, property-level security is not among the list of features of this framework, so you'll need to do some custom additional work to make your application support it. Of course this problem was discussed by the community before, but existing approaches are not without problems, so I guess there's nothing wrong to return to this discussion and show how this task may be solved in a simple and efficient way.

WCF RIA Services supports RequiresRole attribute for specifying access to entity's Get, Insert, Delete and Update method, but don't do the same for properties - which would be quite logical and, I hope, RequiresRole for properties will be supported in future versions. But for now, we'll need to invent something to be able to implement this functionality.

So, let's suppose we have to restrict access to Person.FirstName field so it will be visible only for administrator. One of the creators of WCF RIA Services, Brad Abrams in his blog suggests the following solution: inside the Get-method, iterate all objects in ObjectContext and clear the information you don't want to be shown for this particular case. Something like that:

        public IQueryable<Person> GetPersons()
        {
            foreach (var person in ObjectContext.Persons)
            {
                if (!this.ServiceContext.User.IsInRole("Administrators"))
                {
                    person.FirstName = null;
                }
            }
 
            return ObjectContext.Persons;
        }

The main problem with this code is performance. This method returns IQueryable, and the main power of IQueryable is that it can be filtered with any filter directly from the Silverlight client, and this filter through LINQ goes directly to SQL query executed on database. This way, you load only objects you actually need, so that client-side filter like

EntityQuery<Person> eq = myDomainContext.GetPersonsQuery().Where(p => p.LastName == "Turner");

will be transformed into SQL query

SELECT [Extent1].[PersonID] AS [PersonID], [Extent1].[LastName] AS [LastName], [Extent1].[FirstName] AS [FirstName], [Extent1].[Birthday] AS [Birthday], [Extent1].[JobId] AS [JobId] FROM [dbo].[Persons] AS [Extent1] 
WHERE N'Turner' = [Extent1].[LastName] ORDER BY [Extent1].[LastName] ASC

so even on server-side, you'll load only data that you actually need, only persons whose last name is Turner. All this magic is broken by this treatment of security: if you iterate the collection before returning IQueryable, you actually load all existing persons and wrap them in objects! So if you'll profile the aforementioned sample, you'll see two SQL queries, one loads all persons, another one - filters them:

SELECT [Extent1].[PersonID] AS [PersonID], [Extent1].[LastName] AS [LastName], [Extent1].[FirstName] AS [FirstName], [Extent1].[Birthday] AS [Birthday], [Extent1].[JobId] AS [JobId] FROM [dbo].[Persons] AS [Extent1] ORDER BY [Extent1].[LastName] ASC
 
SELECT [Extent1].[PersonID] AS [PersonID], [Extent1].[LastName] AS [LastName], [Extent1].[FirstName] AS [FirstName], [Extent1].[Birthday] AS [Birthday], [Extent1].[JobId] AS [JobId] FROM [dbo].[Persons] AS [Extent1] 
WHERE N'Turner' = [Extent1].[LastName] ORDER BY [Extent1].[LastName] ASC

So, as you see, this kind of security checks may cause serious problems with performance. How it can be solved? Actually, in two ways:

1. As it was suggested by Jeremy Danyow in comments to the same blog article, you may check security on ObjectContext's ObjectMaterialized event. This way the security will be checked only for objects that are actually loaded. But this solution is too specific for Entity Framework, however the power of WCF RIA DomainService is that it can wrap any data, not necessarily the data from Entity Framework. Besides, it doesn't seem a good solution from architectural point of view: if you solve entity-level security on a WCF RIA layer, why should you solve property-level security on lower layer (not through WCF RIA Services, but through Entity Framework)?

2. The approach we used in our project: in DomainService, override Query method and check security after the object was loaded:

    public partial class MyDomainService
    {
    ...
        public override System.Collections.IEnumerable Query(QueryDescription queryDescription, out IEnumerable<System.ComponentModel.DataAnnotations.ValidationResult> validationErrors, out int totalCount)
        {
            IEnumerable enumerable = base.Query(queryDescription, out validationErrors, out totalCount);
 
            //check roles
            foreach (var obj in enumerable)
            {
                if (obj is Person && !this.ServiceContext.User.IsInRole("Administrators"))
                {
                    (obj as Person).FirstName = null;
                }
            }
            return enumerable;
        }
    }

After executing base.Query, all filters are already applied, and load has already happened, so iterating the returned collection won't result in loading excessive data. Besides, this method is called for every your Get request, so you can implement your own generalized security check. For example, you may even support your own RequiresRole attribute that can be applied to properties, and check this metadata in Query method to clear the fields you need. So overriding Query and checking security on properties gives you both good performance and a good way to generalize security treatment, besides the problem can be solved for any type of DomainService and not only for LinqToEntitiesDomainService, which is, certainly, another advantage.

Comments

Can't this be done earlier?

Hello, very nice article. Thanks.
What I need is to filter all my queries to only return the data for a given ApplicationID.
But here in the query overload, it's already too late. All the data has already been loaded.
And by default, I get all the rows for all the applications when I only need the data for a specific one.
Is there a OnBeforeQuery event so that I can add a where clause to the IQueryable?
That way I can filter out the date and only load the relevent rows...
What do you think?

ApplicationID

ApplicationID is not a Role that needs to be looked up. Roles are dynamic. The ApplicationID should be static.
So, you should just add a Where clause to all of your queries - Where ApplicationID = ApplicationIDCurrent

For instance: public

For instance:

public IQueryable GetPersons()
{
return ObjectContext.Persons.Where(p => p.ApplicationID == gApplicationID);
}

or

public IQueryable GetPersons(int applicationID)
{
return ObjectContext.Persons.Where(p => p.ApplicationID == applicationID);
}

Of course, if you are trying to null out a column like the above code, then linq is not capable while maintaining the type.

Performance yes, security no

Since the where clause is sent to the database before the onmaterialised is performed, a bad client can send a queries like:
Does the firstname start with an A, Does the firstname start with a B, etc.
This would perform like an oracle and still result in a information leak.

This problem can be solved

First of all, thanks for mentioning this possible information leak. I must add that actually this problem is valid for every implementation of property-based security using WCF RIA, including the implementation described in the first article on this topic on MSDN blogs.

With a little extra effort this problem could be solved. In the same overridable Query method, you can analyze QueryDescription by taking its queryDescription.Query.Expression and then analyzing which properties it uses, see for example
http://stackoverflow.com/questions/671968/retrieving-property-name-from-...
If where clause is setting filters or otherwise uses properties whose values this user shouldn't see, we can return error or empty data.

I will describe this approach in more detail in one of the future articles.