Securing/omitting selected properties of a domain entity in NHibernate (subclass, projection, ?)
Consider the following simplified scenario:
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// restricted
public string SocialSecurityNumber { get; set; }
// restricted
public string MothersMaidenName { get; set; }
}
So, in the application, many users can view Person
data. Some users can view all; other users can view only Name
and Age
.
Having the UI display only authorized data is easy enough on the client side, but I really don't want to even send that data to the client.
I've tried to achieve this by creating a FullPerson : BasicPerson
hierarchy (table-per-class-hierarchy). I used two implementations of a StaffRepository
to get the desired type, but the necessary casting fails at runtime because of the NH proxies. Of course in the RDBMS any given row in the People
table can represent a FullPerson
or a BasicPerson
, and giving them both the same discriminator value does not work either.
I considered mapping only FullPerson
and using AliasToBean
result transformer to filter down to BasicPerson
, but I understand this to be a one-way street, whereas I want the full benefit of entity management and lazy loading (though the example above doesn't include collections) in the session.
Another thought I had was to wrap up all the restricted fields into a class and add this as a property. My concerns with this approach are several:
- It compromises my domain model,
- I'd ha开发者_JS百科ve to declare the property as a collection (always of 1) in order to have it load lazily, and
- I'm not even sure how I'd prevent that lazy collection from loading.
All this feels wrong. Is there a known approach to achieve the desired result?
clarification:
This in an intranet-only desktop application; the session lives on the client. While I can certainly create an intermediate service layer, I would have to give up lazy loading and change tracking, which I'd really like to keep in place.
First, let me say that I do not think it is NHibernate's responsibility to handle security, and data redaction based on same. I think you're overcomplicating this by trying to put it in the data access layer.
I would insert a layer into the service or controller that receives this data request from the client (which shouldn't be the Repository itself) and will perform the data redaction based on user permissions. So, you'd perform the full query to the DB, and then based on user permissions, the service layer would clear out fields of the result set before returning that result set over the service connection. It is not the absolute most performant solution, but is both more secure and more performant than sending all the data to the client and having the client software "censor" it. The Ethernet connection between DB and service layers of the server architecture can handle far more bandwidth than the Internet connection between service layer and client, and in a remote client app you generally have very little control over what the client does with the data; you could be talking to a hacked copy of your software, or a workalike, that doesn't give two flips about user security.
If network bandwidth between service and DB is of high importance, or if a LOT of information is restricted, Linq2NH should be smart enough to let you specify what should or shouldn't be included in a query results using a select list:
if(!user.CanSeeRestrictedFields)
var results = from p as Repository.AsQueryable<Person>()
//rest of Linq statement
select new Person {
Name = p.Name,
Age = p.Age
};
else
var results = from p as Repository.AsQueryable<Person>()
//rest of Linq statement
select new Person {
Name = p.Name,
Age = p.Age,
SocialSecurityNumber = p.SocialSecurityNumber,
MothersMaidenName = p.MothersMaidenName
};
I do not know if Linq2NH is smart enough to parse conditional operators into SQL; I doubt it, but on the off-chance it's possible, you can specify conditional operators in the initializer for the SSN and MMN fields based on whether the user has rights to see them, allowing you to combine these two queries.
I would leave your domain model alone, and instead use Automapper to map to specific DTOs based on the security level of the current user (or whatever criteria you use to determine access to the specific properties). This should be done in some sort of service layer that sits between your UI and your repositories.
Edit:
Based upon your requirements of keeping lazy-loading and change tracking in place, perhaps using the proxy pattern to wrap your domain object is a viable alternative? You could wrap your original domain model in a proxy that performed your security checks on each given property. I believe CSLA.NET uses a method like this for field-level security, so it may be worth browsing the source to get some inspiration. You could perhaps take this one step further and use interfaces implemented by your proxy that only exposed the properties that the user had access to.
精彩评论