Ayende @ Rahien

Refunds available at head office

Rhino Security: Part 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)

image

Here is the main interface for Rhino Security:

image

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.

Comments

Peter F.
01/24/2008 03:31 AM by
Peter F.

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:

  1. Can this user perform this operation

  2. Can this user perform this operation on this/these entities

  3. 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.

Ayende Rahien
01/24/2008 04:17 AM by
Ayende Rahien

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.

Robert M.
01/24/2008 09:02 AM by
Robert M.

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

Ayende Rahien
01/24/2008 09:09 AM by
Ayende Rahien

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.

Marco
01/24/2008 02:06 PM by
Marco

Can you show us an example on how to configure Rhino.Security in a real world project (not test suites)?

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.

Ayende Rahien
01/24/2008 02:09 PM by
Ayende Rahien

Macro,

Feel free to send them, I would love to see that.

Peter F.
01/24/2008 02:41 PM by
Peter F.

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.

Comments have been closed on this topic.