Field Level Access with RIA Services

There are lots of reason you may need to customize the access to given fields within an entity.  For example, HIPPA compliance requires that some data not be exposed to only employees with a need to know.   It is often not sufficient to just NOT show the data in the Silverlight client, you need to not even send it over the wire. 

This example works with Silverlight 4\RIA Services Beta and Visual Studio 2010 Beta2

I built a very simple RIA Services + Silverlight 4 example to show how this could be done.   First, let’s run the app, then we can look at how we built it.

The first thing to notice is when we run it, no users are logged in, so we get no access to the data at all. 

image

First, let’s log in as a Rocky, who is a jr. employee at our company.  He should NOT have access to the social security numbers of employees, but the other information is good for him to be able to access.

image

As you can see, no SSNs are displayed.

Now, let’s log in as Billy, who is our HR Manager…  As you can see, Billy has a need to know what the SSN is for most employees, so those are visible to him.  But notice, even he can not see VP level personal information. 

image

OK, now let’s look at how we implemented this.   Really the key code is the domain service on which runs on the server:

   1:     [RequiresAuthentication]
  2:     [EnableClientAccess()]
  3:     public class EmployeesDomainService : LinqToEntitiesDomainService<NORTHWNDEntities>
  4:     {
  5: 
  6:         public IQueryable<Employee> GetEmployees()
  7:         {
  8:             foreach (var e in this.ObjectContext.Employees)
  9:             {
 10:                 if (!this.ServiceContext.User.IsInRole("HRManagers"))
 11:                 {
 12:                     e.SSN = null;
 13:                 }
 14:                 else if (e.Title.Contains("Vice President"))
 15:                 {
 16:                     e.SSN = null;
 17:                 }
 18:             }
 19:             return this.ObjectContext.Employees;
 20:         }
 21: 
 22: 

In line 1, we mark this services are only accessible to users that are logged in.

In line 10, we are making sure that only the user making the request is in the role that enables them to have access to the SSN, if not, we null it out.

In line 14, we have a (lame) example to show accessing data on the entity to decide if the user should have access.  In this case, even the HRManager can’t access the VP’s SSN. 

 

Some notes on running the app:

  • Download the source code
  • Billy and Rocky’s passwords are “password1!”
  • Be sure the refresh the page after logging in or out
  • You can customize the roles by using the IIS Admin tool or the ASP.NET configuration properties on the Web solution

 

Enjoy!

Comments

  • Anonymous
    December 08, 2009
    Do you have an example of how you would handle an update?  

  • Anonymous
    December 08, 2009
    Using this technique, what happens when Rocky updates an entity?  Since the server sent null for the SSN will his update of Address, City, Birth Date cause the SSN field to be set to null in the database?

  • Anonymous
    December 08, 2009
    The comment has been removed

  • Anonymous
    December 08, 2009
    The comment has been removed

  • Anonymous
    December 08, 2009
    The comment has been removed

  • Anonymous
    December 08, 2009
    good workaround, will be nice if can use attribute to specified and remove the properties during runtime.

  • Anonymous
    December 08, 2009
    This should be handled through Presentation Model, not directly through entities. To hide column we can have additional IsSSNVisible property in presentation model and bind DataGridTextColumn.Visible to it.

  • Anonymous
    December 09, 2009
    Maybe their should be a feature in RIA like in SYNC framework 'DataColumns.AddRange' where the columns can be filtered at the server. Use this with IsInRole to return only columns required as we do in SYNC now. A better option would be to declaritavely add a [roles(role1,role2)] to the Metadata DomainService class. Either way the client side grid etc would need the ability to not display a column if the column data is missing from the ria data.  

  • Anonymous
    December 09, 2009
    Is there any way to do this in a base class? My existing ancient C++ BOL/DAL allows for per table / per user (or user group) security customization which is stored in the db itself. In other words, a sys admin can configure all CRUD operations of a table and all read/read-write/no-access privs on a per column basis based on a user's name or domain group. I would like to move to entity framework / RIA and am having trouble figuring out how to do this.

  • Anonymous
    December 09, 2009
    I believe in real world DBA or HR system won't let query fields like SSN and salary. System should have views and not need to worry about application level hard coding. This could lead to big issue when adding or changing role would cause fields like ssn visible to unintended users

  • Anonymous
    December 14, 2009
    Alex, if you leave this sort of thing to the Presentation Model, then all the info still goes across the wire and is still available to the user with little effort.

  • Anonymous
    December 21, 2009
    How about implementing RequiresRole in entity metadata for property-level access? That way, the domain service could serialize only the properties that the user has access. I think thats very straight forward.

  • Anonymous
    January 01, 2010
    I've done this using a bit of AOP magic where a WCF Channel intercepts this and decides based on role/claim if the access to some data should be ommited and in this case, it would simply set this property to it's default value. That solution isn't complete as some of you stated. Also when the user send the data back the DAL needs to avoid updating those fields. This proves to be a bit tricky as some ORMs don't have the proper hooks. We were using a "hacked" version of Linq2SQL where we were manually doing all data modifications (yes it sounds ugly and you better not do it, don't use L2S for big apps, we hit some road block and there was no way back) The other missing part is the UI. This isn't really for security just to provide a better user experience. We have some attached behaviors that allow us to hide any UIElement based on role/claims. It looks something like <dg:Colum Security:Visible.ToRole="Managers"/> It was really easy to implement. The namespace can be confused, this isn't about security, this is just for UserExperience. The only way to secure your data is not sending it as Brad mentioned, which was the actual point of the blog post.

  • Anonymous
    March 05, 2010
    here is a solution that won't require any changes to the domain service's query and update methods: [EnableClientAccess] public class DemographicService : LinqToEntitiesDomainService<DemographicEntities> {    protected override DemographicEntities CreateObjectContext()    {        var context = base.CreateObjectContext();        context.ObjectMaterialized +=            (s, e) =>            {                                if (this.ServiceContext.User.IsInRole("Administrator"))                    return;  //user is Admin... no need to secure fields                Client client = e.Entity as Client;                if (client == null)                    return;  //not an entity we care about securing...                //suppress client ssn and medical policy number                client.Ssn = string.Empty;                client.MedicalPolicyID = string.Empty;                //reset change tracking                this.ObjectContext.ObjectStateManager                    .ChangeObjectState(e.Entity, EntityState.Unchanged);            };        return context;    }    . . .    . . .          . . .     }

  • Anonymous
    March 06, 2010
    Excellent Jeremy, I'll keep that in mind, it's still a lot more work than what I would expect, but it's a step int he right direction. I think this could be extended to provide  set of strategies that knows how to deal with different types. I'm think in something like public class ClientSecurityProvider : SecureEntityStrategy<Client> {  public void Secure(Client entity){    client.Ssn = string.Empty;    client.MedicalPolicyID = string.Empty;  } } Then the framework will take care of determining which strategies to call and do set the state to UnChanged.