Postsharp: authorization and authentication

Original author: Matthew D. Groves
  • Transfer
One of the most popular applications of aspect-oriented programming is the removal of the serving infrastructure code, which is often repeated in the system, in a separate class. Which in turn is a manifestation of the principle of single responsibility (SRP - Single Responsibility Principle). Very often, authorization and authentication tasks are scattered throughout the code, checking user access rights. The consequence of this is the great laboriousness of changes in this important part of the logic, as well as its general verification. The principle of single responsibility suggests that there should be only one reason for changing the class, so that everything related to authorization and authentication is simply requested in separate classes.

Basic authentication methods are usually not what is used throughout the application. For example, in web applications, data input for authentication and authentication itself occurs on one page, after which user information is stored in some token with an expiration date. Thanks to this, the user can automatically log into the system during the term of the token. Thus, the only code that permeates all pages of the application will be to verify that the user is still on the system. Of course, you can use PostSharp for such a check, but this is not the best way to apply, in my opinion.



Authorization, by contrast, is a good example of using PostSharp. Too often, one can see how the logic of resolving actions based on user roles is terribly blurred throughout the application. Just in such situations, PostSharp can be used to remove the “dirt” and leave only meaningful code. In addition, sometimes role-based security is too extensive and you need a thin tool to control access. For example, restrict data editing to everyone except the creator. So, let's look at a simple application, which is very similar to the one I worked with as a consultant and we will figure out how PostSharp can help.

Let the test application help people fill out some requests for public services. It may be a web application, but we will use the WinForms application for simplicity. Any user can fill out a request form, in our case it will be the only text field. The system administrator can delete any requests, a simple user can only create them and view existing ones.

I will not give here the full code of the program, I will limit myself to a service class that provides basic functionality for the indicated actions. He will use the static collection as a storage, but in this application, of course, databases, services and more will be used.
public class GovtFormService : IGovtFormService{
	private static readonly IList _govtFormsDatabase = new List();
	public GovtFormService() {
		// build up some initial entries of the static list
	}
	public void SubmitForm(GovtForm form) {
		_govtFormsDatabase.Add(form);
	}
	public IEnumerable GetAllForms() {
		return _govtFormsDatabase;
	}
	public GovtForm GetFormById(Guid guid) {
		return _govtFormsDatabase.FirstOrDefault(form => form.FormId == guid);
	}
}

Here, we simply associate the form with the service and assign methods from the service to the buttons to ultimately get the basic application. However, the requirements clearly indicate that the user should be able to view only their requests. GetFormById currently does not do any checks. You can add several conditions, but it’s better to immediately create an aspect that can be used everywhere.
[Serializable]
public class AuthorizeReturnValueAttribute : OnMethodBoundaryAspect{
        [NonSerialized] private IAuth Auth;
        public override void RuntimeInitialize(System.Reflection.MethodBase method) {
                Auth = new AuthService();
        }
        public override void OnSuccess(MethodExecutionArgs args) {
                var singleForm = args.ReturnValue as GovtForm;
                if (singleForm != null) {
                        if(Auth.CurrentUserHasPermission(singleForm, Permission.Read)) {
                                MessageBox.Show(
                                 "You are not authorized to view the details of that form",
                                 "Authorization Denied!");
                                args.ReturnValue = null;
                        }
                        return;
                }
        }
}

One way to implement this is to check if the intercepted method returns a GovtForm type. If yes, then check whether this request form belongs to the user. Note that a field of type I Auth is marked as non-serializable, and that it is initialized in the overridden RuntimeInitialize method. In this case, the hardcode was used, but you can use the IoC container as a service locator (see the translation about Dependency Injection).

Add some more code to the aspect so that you can work with methods that return the GovtForm collection and filter the available request forms for the current user.
var formCollection = args.ReturnValue as IEnumerable;
if (formCollection != null) {
        args.ReturnValue = formCollection
                        .Where(f => Auth.CurrentUserHasPermission(f, Permission.Read));
        return;
}

The GovtForm class is unremarkable except that it implements the UserName property. You can add the ISecurable interface to each business object that you want to process in accordance with the proposed approach. Then, the CurrentUserHasPermission method can be rewritten to accept an argument of type ISecurable. After that, it remains only to add the AuthorizedRecordsOnly attribute to all services and repositories that return a single business object or their collection so that the user sees only their request forms. With this approach, you do not have to significantly change the code for the UI or services.

Now that you are already familiar with some useful aspects, it is time to consider a more comprehensive example. Imagine you have both an aspect for caching ANDauthorization aspect for a particular method. It is logical to assume that caching should go after authorization, otherwise access to cached data can be obtained without access rights. So how do you get the authorization aspect to work before caching?

Here's how to do it:
  1. Apply the ProvideAspectRoleAttribute attribute to the aspects of interest. The role can be a string, or you can use the StandardRoles enumeration from the PostSharp space.
  2. Apply the AspectRoleDependencyAttribute attribute to the aspects of interest to indicate the type of dependency and the role on which they depend.

To solve the problem mentioned above, you need to apply atrbits to aspects something like this:
[Serializable]
[AspectRoleDependency(AspectDependencyAction.Order,
        AspectDependencyPosition.Before, StandardRoles.Caching)]
public class AuthorizeReturnValueAttribute : OnMethodBoundaryAspect{  }
[Serializable]
[ProvideAspectRole(StandardRoles.Caching)]
public class CachingAttribute : OnMethodBoundaryAspect {
        public override void OnEntry(MethodExecutionArgs args) {
                // do caching stuff
        }
        public override void OnSuccess(MethodExecutionArgs args) {
                // do caching stuff
        }
}

Using this code, we told PostSharp that the caching aspect is being created with the Caching role, and we have also explicitly stated that the authorization aspect must be applied before any other aspect with the Caching role takes effect.

And here is how the code of the GetAllForms method will look after both aspects are applied (obtained using a program in the spirit of Reflector):
public IEnumerable GetAllForms(){
        MethodExecutionArgs CS$0$2__aspectArgs = new MethodExecutionArgs(null, null);
        <>z__Aspects.a1.OnEntry(CS$0$2__aspectArgs);
        IEnumerable CS$1$1__returnValue = _govtFormsDatabase;
        <>z__Aspects.a1.OnSuccess(CS$0$2__aspectArgs);
        CS$0$2__aspectArgs.ReturnValue = CS$1$1__returnValue;
        <>z__Aspects.a0.OnSuccess(CS$0$2__aspectArgs);
        return (IEnumerable) CS$0$2__aspectArgs.ReturnValue;
}

And for comparison, the code, if everything is correct and change AspectDependencyPosition to Before:
public IEnumerable GetAllForms(){
        <>z__Aspects.a1.OnEntry(null);
        MethodExecutionArgs CS$0$2__aspectArgs = new MethodExecutionArgs(null, null);
        IEnumerable CS$1$1__returnValue = _govtFormsDatabase;
        CS$0$2__aspectArgs.ReturnValue = CS$1$1__returnValue;
        <>z__Aspects.a0.OnSuccess(CS$0$2__aspectArgs);
        CS$1$1__returnValue = (IEnumerable) CS$0$2__aspectArgs.ReturnValue;
        <>z__Aspects.a1.OnSuccess(null);
        return CS$1$1__returnValue;
}

Note that a1 (caching) and a0 (authorization) are swapped.

PostSharp provides extensive options for configuring aspect dependencies. At your disposal 5 actions for resolving dependencies: Commute, Conflict, Order, Require, and None. In addition, you can create an infinite number of roles if they are not enough in the StandardRoles enumeration. Fortunately, you do not often need these features, but it's good to know about them to use when the time comes. The best part is that you can relax, knowing that even the newest member of the team will not give out data on the UI that needs authorization, since it will not matter in what order he applies the aspects to the code.

Matthew D . Groves is a software engineer at Telligent . His blog mgroves . com .

PostSharp : Logging and auditing , caching , delayed dependency loading .

Also popular now: