Rhino SecurityPart II - Discussing the Implementation
I just finished testing an annoying but important feature, NHibernate's second level cache integration with Rhino Security. The security rules are a natural candidate for caching, since they change to change on an infrequent basis but are queried often. As such, it is obvious why I spent time ensuring that the whole thing works successfully.
At any rate, what I wanted to talk about today was structure of the framework. You can see the table layout below.
A few things to note:
- The tables are all in the "security" schema, I find that it makes more sense this way, but you can set it to work with "security_Operation" if you really like (or the DB doesn't support schemas).
- User is referenced a few times, but is not shown here. As I mentioned earlier, the user entity is an external concept. We are using the mapping rewriting technique that we discussed earlier.
(more below)
Here is the main interface for Rhino Security:
The tasks it performs are:
- Enhance a query with security information.
- Get an explanation about why permission was given / denied.
- Perform an explicit permission check on entity or feature.
The last two are fairly easy to understand, but what does the last one means? It means that if I want to perform a "select * from accounts" and I enhance the query, I will give this baby:
SELECT THIS_.ID AS ID4_0_,
THIS_.SECURITYKEY AS SECURITY2_4_0_,
THIS_.NAME AS NAME4_0_
FROM ACCOUNTS THIS_
WHERE @p0 = (SELECT THIS_0_.ALLOW AS Y0_
FROM SECURITY_PERMISSIONS THIS_0_
INNER JOIN SECURITY_OPERATIONS OP1_
ON THIS_0_.OPERATION = OP1_.ID
LEFT OUTER JOIN SECURITY_ENTITIESGROUPS ENTITYGROU2_
ON THIS_0_.ENTITIESGROUP = ENTITYGROU2_.ID
LEFT OUTER JOIN SECURITY_ENTITYREFERENCESTOENTITIESGROUPS ENTITIES7_
ON ENTITYGROU2_.ID = ENTITIES7_.GROUPID
LEFT OUTER JOIN SECURITY_ENTITYREFERENCES ENTITYKEY3_
ON ENTITIES7_.ENTITYREFERENCEID = ENTITYKEY3_.ID
WHERE OP1_.NAME IN (@p1,@p2)
AND (THIS_0_."User" = @p3
OR THIS_0_.USERSGROUP IN (@p4))
AND ((THIS_.SECURITYKEY = THIS_0_.ENTITYSECURITYKEY
OR THIS_.SECURITYKEY = ENTITYKEY3_.ENTITYSECURITYKEY)
OR (THIS_0_.ENTITYSECURITYKEY IS NULL
AND THIS_0_.ENTITIESGROUP IS NULL))
ORDER BY THIS_0_.LEVEL DESC,
THIS_0_.ALLOW ASC);
Isn't it cute? This is much better than some home grown security system that I have seen (some of which I have built, actually), since it is stable with regards to the cost of the query that it will use. I mention before that it is possible to de-normalize the information into a single table, but since this requires more invasive approach from the application side, and since I have not seen performance issues with this yet, I'll leave it at this point for the moment.
Advance: Working with non DB backed users
In most applications, the user is a fully fledged entity, even if your authentication is elsewhere. Just keeping things like preferences will necessitate that, but let us assume that we really have that as a non DB entity.
In this case, we would need to make modifications to the tables (which we generally do, anyway, although automatically), and re-map the user references as an IUserType that can get the user from an id instead of a foreign key to entity. I think I'll wait until someone really needs it to make a demo of it.
Anyway, this is the main magic. It was fairly hard to get it right, as a matter of fact. But the query enhancement was one of the things that made security a breeze in my last project. We had a custom security module, but many of the concepts are the same.
The next part we will talk a bit about the IsAllowed and how that works. There is some nice magic there as well.
More posts in "Rhino Security" series:
- (25 Jan 2008) External API
- (24 Jan 2008) Part II - Discussing the Implementation
Comments
Ayende, could explain your vision of the EntityGroup a bit more? I quoted your previous comments on the concept but it's not clear to me what the purpose is.
"Permissions can be granted and revoked on an Entity Group, and those are applicable to all the entities that are member in this group. This way, business logic that touches permissions doesn't need to be scattered all over the place, when a state change affects the permissions on an entity, it is added or removed to an entity group, which has a well known operations defined on it."
-- Wouldn't an entity type have well-known operations defined on it already (i.e., Account.Edit)? And aren't the permissions just the set of privileges within the available operations?
"An Entity Group is a way to specify permissions for a set of entities, while the key is the set of permissions on a specific entity"
-- Are you intending the EntityGroup to be a security hierarchy? Is I create the AccountGroup with Account.View permission set on it, what is the affect on members of that entitygroup and why?
In my own acl security implementation I associated an entity with a default set of permissions that served for any requesting identity NOT in the acl entries table. Is that the point of the EntityGroup?
Also, what do you think about a rule extension point to the entry table for a contextual rule. This could be the Policy or set of policy rules which might be "Valid between 9am and 5pm M-F", and "If amount < 1000". The gates are:
Can this user perform this operation
Can this user perform this operation on this/these entities
Can this user perform this operation on these entities under the current context.
I'm not sure how one would load up and execute such a rules engine for lists but it could be applied as a specification to filter out the returned entities.
Peter,
I'll answer about entities group in another post.
About adding rules as well, this is tricky, because how am I going make those kind of rules work in the database?
I can deal with most of those through the entities group, except the time based one, I am afraid.
Ayende,
Can you show us an example on how to configure Rhino.Security in a real world project (not test suites)?
I am particularly interested in how would you organize and setup
IEntityInformationExtractor classes for multiple domain objects and how would you initialize them in Windsor.boo?
Do I have to write "component IEntityInformationExtractor of Account, AccountInfromationExtractor" for each PONO that I have in my app and for which I want to apply security?
Thanks,
Robert
Robert,
I assume that most of the time, you have a layer super type for your project, so you can register a single one and let the container resolve it for each type, generically.
That would be great.. for example in your Hibernating.Forums sample .. if you add the securtiy layer and your ideas on some controller / entities / group of entities as a sample, i can send you patches for the details.
Macro,
Feel free to send them, I would love to see that.
Right, I don't think you could run a rules filter in the database (unless your rule could be expressed as SQL) but you could provide a filter in-memory that uses the association during the load process. I was thinking of something like:
Policy_Table
PolicyId
RuleId
RuleType
and a one-to-many association between the permission entries table to the Policy table. The Policy is just a collection of rule specifications that can determine the requesting context and each entity that is built is intercepted like Filter(Policy.IsSatisfiedBy(entity));
Not sure that NHibernate could do this, but the larger question is whether a security layer can exist solely in the database. I think that's an interesting problem.
Comment preview