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.
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.
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.
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 removedAnonymous
December 08, 2009
The comment has been removedAnonymous
December 08, 2009
The comment has been removedAnonymous
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 usersAnonymous
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.