Rhino Security Overview: Part I
A few months ago I spoke about how I would build a security infrastructure for an enterprise application, I went over the design goals that I had and the failing of previous attempts.
Since then, I got a few requests to implement that, and since this is really neat piece of work, I decided to go ahead and build it.
The main goals, allow me to remind you, are:
- Flexible
- Performant
- Does not constrain the domain model
- Easy to work with
- Understandable
- No surprises
You can read the post about it to get a feeling about what I had in mind when I thought it up.
When it came the time to actually implement them, however, it turn out that as usual, the implementation is significantly different than the design in all the details. Here is the overall detail of the entities involved.
The main part that we have here is the security model, but notice that the application domain model is off to the side, and have very little to do with the security model. In fact, there is only a single requirement from the application domain model, the User entity needs to implement an interface with a single property, that is all. (more below)
From the point of view of interactions with the security system, we have two extensions points that we need to supply, a User entity implementing IUser, and a set of services implementing IEntityInformationExtractor<TEntity>:
The use of IEntityInformationExtractor<TEntity> is an interesting one. Rhino Security is using all my usual infrastructure, so it can take advantage of Windsor' generic specialization to do some nice tricks. Components of IEntityInformationExtractor<TEntity> are registered in the container, and resolved at runtime when there is a need for them.
This means that you can either implement a generic one for your model, or specialize as needed using generics. I like this approach very much, frankly.
From the point of view of the security model, we have the following:
- User - the external User entity that is mapped into the security domain. Permissions are defined for users.
- Users Group - a named grouping of users. Permissions are defined for users groups.
- Entity - an external entity. Permissions are defined on entities.
- Entities Groups - a named groups of entities. Permissions are defined on entities groups.
- Operation - a named operation in the system, using the "/Account/Financial/View" pattern.
- Permission - Allow or deny an operation for user / users group for an entity / entities group.
Here are a few examples of the kind of rules that we can defined with this method:
- Allow "/Account/Financial/View" for "Ayende" on "All Accounts", level 1
- Allow "/Account/Financial/View" for "Accounting Department" on "All Accounts", Level 1
- Allow "/Case/Assign" for "HelpDesk" on "Standard cases", level 1
- Allow "/Employee" for "Joe Smith" on "Direct Reports of Joe Smith", level 1
The major shift here is that we treat both entities and users groups as very cheap resources. Instead of having a just a few and structuring around them, we define what we want and then structure the groups around them. The burden them moves from complex code to maintaining the structure. I find this a very reasonable tradeoff for a simple security model and the flexibility that it gives me.
Next time, setting up the security model...
Comments
Very interesting! I would like to see code samples!
I'm curious, why is the Operation part of the security model? That seems more application or domain specific.
Peter,
Operation is a security concept, not a domain concept. It is the operation that you are performing that you get permission for.
I have rarely seen it as a first class entity in projects, when it appeared, it was usually in the security module anyway.
Can you give an example of when this is part of the domain?
Oren,
i anxiously want to give it a spin, but i'm having an issue building rhino-tools from the ide ( complaints about missing AssemblyInfo.cs when i try building rhino commons).
Check out the "How to build.txt" file, you need to run the build from the command line first.
I'm reading "that you are performing" as something the user would be performing, not the programmer. That to me seems like application behaviour that would be tied (a least sometimes) to the domain. e.g. the operation of "printing": a user may or may not have the permission to print; but "printing" would be a domain concept--some domains deal with printing, some do not. A "permission" would be a security concept. i.e. I don't see the need for the security model to know anything about an operation (as described above), the security layer would define who would have what permission and the application/domain would then decide whether or not it can invoke a operation based on the user's permissions.
Would the application/domain ask the security model if a user has a particular permission, or would you ask the security model if they are able to perform a printing operation? The later requires that the security model knows about "printing"--a circular dependency, the security model needs to know about domain concepts and the domain relies upon the security model for domain concepts (and permission management).
Peter,
We have circular dependencies here, with User, for example/
I think that we need to talk about Operation entity type and Operation instances.
Operation entity type is a security concept.
Operation instances are domain concepts.
So "/Printing" is a domain concept, but the idea of operation itself is part of the security infrastructure.
Ayende,
Please explain a little bit about security levels... What kind of usage scenarios do they cover?
How about the mixing of roles and permissions? I usually group my permissions in roles so that the application's administrative burden is a little bit lighter. It would be nice that if we I grant the user X the role Salesman, then he would have the following list of permissions...etc.
Robert,
Yes, there is support for groups of users.
Those can be local groups "Users associated with Northwind" or global "Sales". The difference is conceptual more than anything else.
The level are used to resolve ambiguities in the permissions, so you can override a permission with another.
Let me check if I get it right: you are saying that, instead of having roles considered as collection of permissions, I can replace this concept with another one like: user group called "Salesman" to which I can grant many permissions?
For me, the security model should only be concerned about permissions. It should be able to tell the application whether a user has a permission and the application would tell the security layer to "set" a permission for a particular user/group/role/etc. (as well as management of users and permissions including hierarchy...) It doesn't need to be concerned about what that permission means to the application, only that that user has it (or wasn't denied it). The application then maps that permission to a specific ability or action and lets the user perform it if they have the permission. That model means the concern of application-specific operations isn't mixed into the security model.
That, of course, doesn't mean what you've defined won't work for Rhino...
Robert,
Yes, that is the overall idea.
Peter,
I have a disconnect here, because that is what Rhino Security does.
Amazing timing, I have been experimenting with an implementation your 'vision for an enterprise security infrasturcture' you posted about last year and have just arranged with a colleague to review/discuss it.
Great post, where do you find the time?
Comment preview