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:
- Does not constrain the domain model
- Easy to work with
- 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...