Designing the Security Model
Right now I want to talk more deeply than merely the security infrastructure, I want to talk about how you use this security infrastructure.
There are several approaches for those. One of them, which I have seen used heavily in CSLA, is to simply make the check in the properties. Something like this:
public class Comment { public virtual IPAddress OriginIP { get { CanReadProperty(true); return originIP; } set { CanWriteProperty(true); originIP = value; } } public virtual bool CanDelete() { ... } }
We can move to a declarative model with attributes, like this:
[SecuredEntity] public class Comment { [SecuredProperty] public virtual IPAddress OriginIP { get { return originIP; } set { originIP = value; } } }
We can take it a step further and decide that we don't want to explicitly state this, and just assume it by convention.
A while ago I decided to make a system work in this exact fashion. Of course, I am a big believer of convention over configuration, so we defined the following rules for the application:
- Need to secure Read / Write / Delete
- Need to support custom operation as well, like "Assign work", "Authorize time sheet", etc
- Developers will forget to make security calls, we need to develop a system that protects us from this.
We built a really nice implementation that hooked directly into the container and the data access later, you literally could not make a security breach, because you were always running under the context of a user, and the data was filtered for you by the data access in a generic fashion. For that matter, we didn't have to worry about authorization in the business code, we made in memory modification and then persisted that. If we had a security violation, we simply showed an error to the user.
We didn't have to define anything in the code either, it was all convention based and implicit. The only complexity was in configuring the security system itself, but that was the nature of the beast, after all. We though that we found the ultimate security pattern, and were quite pleased with ourselves. Until we started user testing. Then we run into... interesting issues.
We had the idea of a rule in the system, which could be used by the administrators to set policies. It worked just fine in our testing, until we started to get impossible errors from the users. I think that you can understand what it was by now, right? (Yes, the usual, developers always test as admin)
Normal users didn't have permissions to read those rules, but the system really needed them to perform core parts of its tasks. We are always running under the user context and we are always making the security check. The system was designed, up front, to be very explicit about it. Now we found that there really were reasons where the system needed access to the entities without performing those security checks.
Another "interesting issue" that came up was the issue of information aggregation. As it turn out, it was always the wrong idea to report that there is only a single entity in the system, just because the user has access to just that one. I'll let you draw the conclusion about the HR users that found out that they really couldn't see how many hours an employee worked in this month.
It was a big problem, and we had to scramble around and hack-a-lot the ultimate security solution that we so loved.
The security system had two major issues:
- It was too granular.
- It didn't have any operating context.
Since then, I have learned a lot about how to design and implement security modules, and my current design has the following requirements:
- Security decisions are made on a use case level
- Have a standard, enforced, approach of dealing with security
- Make security decision easy to code.
How does this translate to code?
Well, first I need to define what a use case is in the code. For a web application, it is almost always at the page / request level, and that makes it very easy to deal with.
In my last project, we had the following base class defined:
public abstract class AbstractController { public abstract AssertUserIsAllowed(); }
Since all the controllers in the application inherited from AbstractController, we had a single place that we put all the security checks. We still had to deal with security in other places, such as when we loaded data from the database, or wanted partial views, but this approach meant that we had remembered what was going on. If we needed to make security decisions elsewhere, we commented that in the assert.
But this was in a Rhino Igloo application, where by necessity we had to have a controller per page. Using MonoRail, we usually have a single controller that handles several actions, in which case I would tend to write something like this:
[AllowEveryone] public void Login() { ... } [AllowAuthenticatedUsers] public void Index() { ... }[ActionPerformsCustomSecurityCheck] public void Save(...) { ... }
And then I would mandate that all actions would have to have security attributes on them (Mandate as in, if it doesn't have security attribute, you can't access it).
Well, actually, right now I would probably route it through Rhino Security, which would give me more flexibility than this model, and would keep it out of the code.
Nevertheless, I would put security at the use case level. This is where I have the context to ask the correct questions. Anything else tend to lead to an increasingly complex set of special rules.
Comments
Very nice post Ayedne. This one goes in my save stack for the next time I need to implement a security system for a client.
Isn't what Role-Based security stands for anyways?
Normally you create roles according to the use cases.
No, role based security isn't about this, because roles doesn't have context.
Do you allow a user to read the rules? In what context?
In the samples you're giving your basically protect running actions, but you don't protect the entities that are used inside that action. How would you protect the entities inside an action?
By context, do you mean data-based security where security is granular to an instance of an entity and not affected to a role that the user plays, whatever the instance of the entity it plays the role (performs the use case) against?
Are your security issues are related to having to grant extra roles (or rights) in order to have access to non-functionnal infrastructure (read rules)?
If so, one approach I've used in the past is to allow impersonation in my security framework so that user can be impersonated to a "system" account, account being able to read security rules.
In other terms, I wouldn't allow the user to directly visualize the rules but would definitely grant him some non-functionnal role that grants him the right to query (read) the rules for authorization purposes.
Hence, I tend to make differences between Read and View
@Marco,
I would wrap my security into specifications and pass those to my repository to resolve and create queries.
AddressBook.Find(new MyContactsSpecification(CurrentUser));
Anyone else?
Marco,
Sorry, I don't understand the meaning of "protecting the entities".
Can you explain?
Francois,
Security per instance is important, certainly.
I shouldn't be able to view another customer's data.
Impersonation is a hack on top of the permissions on data mentality. You don't secure data, you secure operations.
Those are operations:
View Customer Orders
Add Order
Ship Order
Change Shipping Address
Approve Order
They relate to specific data, certainly, but they aren't data in themselves.
It's an extremely interesting topic. Security systems are usually something nobody wants to talk about (or deal with). And in many cases, role based approach works ... with a lot of compromises to the actual security.
But I would also like to hear how you push context to the security subsystem while using declarative approach as in your monorail sample. For example if action rules depend on the data action pulls out.
Bunter,
See my next post for an example
Indead ReadPermissions can be handled at the repository, but how would you protect some properties (like Salary) or prevent writing a property like CompanyName..Shouldn't that not be handled at Entiity level?
@Marco:
The easiest way to protect properties would be to extract them into their own nested entity (component), as a side effect, it usually increases entity cohesion.
Macro,
No.
That is not the way I would go.
You would have several operation for viewing the entity in this case.
View Employee Information
View Employee Salary
This match into the concept of the domain better than just allowing view on the salary property.
The concept here is not protecting data, but securing operations.
Hi Ayende!
I'm always read your posts with big interest!
Could you pointed me on the resources which you used for creation of you security framework? URL-s books code libraries /snippets etc.
Thank you!
This blog, mostly. Check the other posts about it. Especially "Vision of Enterprise Application: Security Infrastructure"
This is not something that I wrote from a reference, but from experience.
Comment preview