📜 ⬆️ ⬇️

Postsharp: authorization and authentication

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, into a separate class. Which, in turn, is a manifestation of the Single Responsibility Principle (SRP). Very often, authorization and authentication tasks are scattered throughout the code, checking user access rights. The consequence of this is the large labor-intensiveness of changes in this important part of logic, as well as its general verification. The principle of common responsibility suggests that there should be only one reason for changing the class, so that everything that relates 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 entry for authentication and authentication itself takes place on one page, after which information about the user is stored in a token with an expiration date. Thanks to this, the user can automatically log in to the system during the token’s lifetime. Thus, the only code that runs through all the pages of the application will be to verify that the user is still on the system. Of course, you can use PostSharp for this 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, you can see how the logic of resolving actions based on user roles is horribly blurred throughout an application. Just in these situations, PostSharp can be used to remove “dirt” and leave only meaningful code. In addition, sometimes role-based security is too extensive and a subtle tool is needed to regulate access. For example, restrict data editing to everyone except the creator. So let's take a look at a simple application that is very similar to the one I worked with as a consultant and see how PostSharp can help.

Let the test application help people fill in some requests to state 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 limit myself to the service class that provides basic functionality for the specified actions. It will use the static collection as a repository, but in this application, of course, databases, services, etc. 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 button presses in order to finally get the basic application. However, the requirements clearly indicate that the user should only be able to view their requests. GetFormById currently does not make any checks. You can add several conditions, but it’s better to create an aspect right away 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 whether the intercepted method returns a type of GovtForm. If yes, then check if the given request form belongs to the user. Notice that the Auth type I field is marked as not 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 the 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 an ISecurable interface to each business object that you want to process according to the proposed approach. Then the CurrentUserHasPermission method can be rewritten to take an argument of type ISecurable. After this, 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 can only see his or her request forms. With this approach, you will not have to significantly change the code for the UI or services.

Now that you are familiar with some useful aspects, it is time to consider a more comprehensive example. Imagine that you have both the caching aspect and the authorization aspect for a particular method. It is logical to assume that caching should go after authorization, otherwise access to the cached data can be obtained without access rights. So how to make the authorization aspect 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 they depend on.

To solve the problem mentioned above, you need to apply attributes to the aspects 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    } } 

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

And this is how the code of the GetAllForms method will look after both aspects are applied (obtained with the help of the Reflector program):
 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 you specify everything correctly 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; } 

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

PostSharp provides ample opportunities for setting aspect dependencies. You have 5 actions to resolve 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 don’t often need these features, but it’s good to know about them to use when the time comes. The most pleasant thing about this is that you can relax, knowing that even the newest member of the team will not issue data to the UI that needs authorization, since it will not matter in what order it applies aspects to the code.

Matthew D. Groves software development engineer in   Telligent . His blog is mgroves . com .

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

Source: https://habr.com/ru/post/125319/


All Articles