A common problem with implementing security is that you end up with bunch of repeated code blocks which check the current user's permissions and then allow or disallow the execution of some method. Example:
public class OrderManagementService : IOrderManagementService
{
public Guid CreateOrder(string orderCode)
{
if(!CallContext.CurrentUser.HasPermission(Permissions.CanCreateOrder))
{
throw new SecurityException("Only users with CanCreateOrder permission can create an order.");
}
Order order = new Order(orderCode);
order.Save(order); // ActiveRecord-like implementation
return order.Id;
}
}
The problem with this approach is that you have to implement the check in every method which requires some combination of permissions. While this is not overly hard to do, it becomes a maintenance hell as the number of such methods grows. Also, if you want to change the behavior in case of missing permissions you have to modify all those functions. Of course, you can encapsulate it in some common utility method, like this:
...
if(!CallContext.CurrentUser.HasPermission(Permissions.CanCreateOrder))
{
HandleMissingPermissions("Only users with CanCreateOrder permission can create an order.");
}
...
This is also not perfect: If you decide that you need more context info in the utility method (e.g. required permissions or method name) you have to modify it:
...
if(!CallContext.CurrentUser.HasPermission(Permissions.CanCreateOrder))
{
HandleMissingPermissions("CreateOrder", Permissions.Admin, "Only users with CanCreateOrder permission can create an order.");
}
...
Now you have to modify all calls to HandleMissingPermissions method, etc.
The issue with application security is that it is a cross-cutting concern: it applies to all parts of system and not to a specific context, therefore, it doesn't make sense to implement it at each point where it is needed. In other words it is an application aspect (in AOP sense), and it is often best implemented in such manner.
There are many ways to implement AOP in .NET world: (Aspect#, NAspect), IL weaving (PostSharp) etc. In my opinion, one of the easiest is using interception features which are provided by some IOC container (e.g. Windsor and Spring.NET have it, StructureMap added it recently). In this example, I will use Windsor, because I am more familiar with it than with the others. For those who somehow missed it, Windsor is a quite popular IOC library, which is a part of Castle, an open source set of tools for easier development of enterprise and web applications.
So, here is how we are going to implement security:
- We are going to define security requirements for each method using attributes
- Instances of the target service (IOrderManagementService) will be retreived through the IOC container
- IOC container will inject the security interceptors
- Interceptor will check whether the method caller has required permissions
Our OrderManagementService class will now look like this:
[Interceptor(typeof(SecurityInterceptor))]
public class OrderManagementService : IOrderManagementService
{
[RequiredPermission(Permissions.CanCreateOrder)]
public virtual Guid CreateOrder(string orderCode)
{
Order order = new Order(orderCode);
order.Save(order); // ActiveRecord-like implementation
return order.Id;
}
}
OrderManagementService class is decorated with [Interceptor] attribute which defines the interceptor class which will be used to wrap the methods. You can define multiple interceptor classes, but usually it is better to do this in configuration file (see attached sample) than directly in the code, because you can switch the interceptors on/off without recompiling the code. This can be useful for debugging purposes or trouble-shooting.
Back to the implementation: security related logic is not checked in CreateOrder method anymore. Instead, SecurityInterceptor reads metadata for each method which is executed and according to RequiredPermission attribute of the method and permissions granted to the current user decides whether the method should be executed or not. Here is the implementation of the SecurityInterceptor:
class SecurityInterceptor : IMethodInterceptor
{
public object Intercept(IMethodInvocation invocation, params object[] args)
{
MethodInfo method = invocation.Method;
if(NeedsAuthorization(method) && GetRequiredPermission(method) != Context.Caller.Permission)
{
string message = string.Format("Method {0} requires {0} permission",
method.Name,
GetRequiredPermission(method));
throw new SecurityException(message);
}
return invocation.Proceed(args);
}
private bool NeedsAuthorization(MethodInfo method)
{
return method.IsDefined(typeof(RequiredPermission), true);
}
private Permission GetRequiredPermission(MethodInfo method)
{
RequiredPermission attribute = (RequiredPermission)method.GetCustomAttributes(typeof(RequiredPermission), false)[0];
return attribute.Permission;
}
}
The only thing left to be done is to modify the client code to retrieve the IOrderManagementService instance via IOC container and call the method:
private void OnCreateOrderClicked(object sender, EventArgs e)
{
// caller permissions have already been set
IOrderManagementService svc = m_container.Resolve<IOrderManagementService>();
svc.CreateOrder("#ord-1-01");
}
This covers all the interesting parts of such implementation. Of course, in real-life scenarios this is not enough. The sample code is a bit more elaborated: you can define custom message for every RequiredPermission attribute instance, permissions can be combined etc.
The same approach can be used for other cross-cutting services, like logging and transaction handling.
You can download the sample here: AopSecurity.zip (123.55 kb)