Designing the Security Model

time to read 5 min | 947 words

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.