Introduction
Introduction to Aspect Oriented Programming (AOP) is mostly done by exercising an example, adding logging as an aspect to an existing application. But, it is really simple to realise more sophisticated functionality using AOP. This article will demonstrate the use of aspects to build an easy to integrate and extensible permission management. This is done by applying custom attributes to methods, and checking these attributes while invoking annotated methods.
Background
Aspect Oriented Programming is targeted to separate code with different goals from each other. For example, a method within a business logic normally contains code to achieve different goals: first, the code making up the logic (e.g., calculating sums, creating a new customer, or everything else I just can't think of now.). Besides this, a method consists of code to write log entries, or to check if the current user has the permission to execute this method. Just think of a business logic method creating a new customer. This method can be called by user A, but not by user B. AOP is an ideal way to separate all these aspects, resulting in code which is better to understand and maintain.
Using the code
The provided code is more a template than a library. You could implement your business logic within the given project using the proposed style. Otherwise, you could examine the template and implement the "Permission-by-aspect" idea on your own. To use the code for your own purpose, you have to do two different things: first, annotating your business logic methods with attributes to define the permissions needed to execute them, and second, implementing a system to check if a user has a specific permission.
Annotating the business logic
Let's assume you need to implement part of a business logic dealing with customers. First of all, as a wise programmer, you declare all methods in an interface. (We will focus here on the CreateCustomer
method.)
public interface ICustomerBusinessLogic {
DataObjects.Customer CreateNewCustomer();
}
Then, there will be a class implementing the interface. Let's assume it looks like this:
public class CustomerBusinessLogic:Interfaces.ICustomerBusinessLogic {
[PermissionManagement.Permission("CreateCustomer")]
public DataObjects.Customer CreateNewCustomer() {
return new DataObjects.Customer {
Id=Guid.NewGuid(),
Name = "Customer",
FirstName = "New",
Address = "Home" };
}
}
The logic itself is not really exciting. A new Customer
object is created and returned. The interesting part is the custom attribute PermissionManagement.Permission("CreateCustomer")
, which says nothing more than the permission "CreateCustomer" is needed to execute this method.
To enable the permission check, we have to interweave the permission aspect with the business logic code above. This could be done by implementing a factory to create a CustomerBusinessLogic
object. Within the factory, the Spring.NET Framework will be used to get the functionality of "interweaving" aspects. The attached PermissionAdvice
is an instance of a class which implements the IMethodInterceptor
interface provided by Spring.NET. The following code shows an example:
public sealed class BusinessLogicFactory {
public Interfaces.ICustomerBusinessLogic CreateCustomerBusinessLogic() {
ProxyFactory facProxy = new ProxyFactory(new CustomerBusinessLogic());
facProxy.AddAdvice(new PermissionManagement.PermissionAdvice());
return facProxy.GetProxy() as Interfaces.ICustomerBusinessLogic;
}
}
The advice named PermissionAdvice
checks if an invoked method is annotated with the Permission
attribute, and will ask the permission management if the current user has the specified permission.
Permission management
As mentioned above, you need some logic to check if a user has a specific permission. Within the example, this is done by a class named PermissionLogic
. This class maintains a dictionary containing a list of all users owning a specific permission. To check if a user has a permission, the logic checks if the list within the dictionary contains the user for the specified permission. If a user does not have the needed permission, an exception is thrown.
if (this.m_dictPermissions.ContainsKey(sCurPermission)) {
if (! ((this.m_dictPermissions[sCurPermission] != null) &&
(this.m_dictPermissions[sCurPermission].Contains(oUser)))) {
throw new MissingPermissionException(oUser, sCurPermission);
}
} else {
throw new MissingPermissionException(oUser, sCurPermission);
}
That is all that is needed to know to implement a business logic on your own with the functionality of permission by aspect.
Points of interest
There are two points of interest that I want to discuss here. First, let's take a look at the implementation of the custom attribute. The PermissionAttribute
inherits the class System.Attribute
. In combination with setting the attribute usage to AttributeTargets.Method
, the PermissionAttribute
can be used to annotate methods. To set the needed permission, the constructor accepts a string containing the permission name. If more than one permission is needed, the names of the permission can be separated by comma.
[System.AttributeUsage(AttributeTargets.Method)]
public class PermissionAttribute:System.Attribute {
public PermissionAttribute(string sPermissionString) {
this.AnalysePermissionString(sPermissionString);
}
These custom attributes will be analysed during the invocation of the specified method by the permission advice. The proxy created by Spring.NET for the ICustomerBusinssLogic
will intercept all method invocations and run the following code instead:
public object Invoke(IMethodInvocation oInvocation) {
object[] aCustomAttributes =
oInvocation.Method.GetCustomAttributes(typeof(PermissionAttribute), true);
if ((aCustomAttributes != null) && (aCustomAttributes.Length > 0)) {
foreach (PermissionAttribute oCurAttribute in aCustomAttributes) {
PermissionLogic.Instance.CheckPermissionsForUser(
UserProvider.CurrentUser, oCurAttribute.GetNeededPermissions());
}
}
return oInvocation.Proceed();
}
Using the passed IMethodInvocation
object, the invoked method and all of its custom attributes can be retrieved. For all PermissionAttribute
s found, the PermissionLogic
will be asked if the current user has the needed permissions. If not, the execution will break, because the logic will throw an unhandled exception, and the invocation of the annotated method is cancelled.
Perspective
This article shows a basic implementation of the permission-by-advice topic. One future addition could be that the method arguments and results will also be checked. This could be useful if users could read customer records, but not all customer records can be changed and saved by all users. When a method like ICustomerLogic.SaveCustomer(a)
is called, the PermissionAdvice
could check if the current user has the permission to save the passed customer a
. The same applies to result objects. Maybe, not all customer records returned from ICustomerLogic.GetCustomers()
are accessible for the current user. The PermissionAdvice
could filter the result and return only the records the user is allowed to view.
Another improvement would be that the custom attributes are not attached to methods of the concrete implementation of the business logic interface, but to the interface itself. In my opinion, this is the better way, because the permissions needed are part of the interface of a component.
Please let me know if you like further examples on this topic, like filtering the result objects or checking the passed arguments.
References