Unity and AOP: Cross-Cutting Security Functionality
With the release of Unity 2.0, a dependency injection container, the team at Altoros explored how to implement the capabilities of aspect-oriented programming with the help of the framework. In this post, we demonstrate how the paradigm works on the example of cross-cutting security functionality injection.
Challenge
In our example, there is a set of repositories that we can access from domain objects—Employees
, Orders
, and Devices
. These objects are placed in different types of storage—Active Directory, external ERP, or a locally stored XML file accordingly. Each repository has different underlying storage, so it is difficult to control user permissions. In our case, we also enable only users with admin roles to perform the create
/edit
/delete
operations for domain objects, while all the other users are limited to the read operations
With the help of NUnit, we have formulated these requirements in the following unit tests.
1. Users with the admin role have the Save
functionality access. We also ran the same test for Delete
.
[Test] public void CheckThatAdminHaveSaveAccess() { // init Thread.CurrentPrincipal = new Principal("Admin"); // action var result = employeeRepository.Save(new List<Employee>() {<span>new <span>Employee()}); // assert Assert.AreEqual(result, 1); }
2. Users with the user role do not have the Save
functionality access (the same for Delete
).
[Test] [ExpectedException(typeof(MethodAccessSecurityException))] public void CheckThatUserHaveNotSaveAccess() { // init Thread.CurrentPrincipal = new Principal("User"); // action var result = employeeRepository.Save(new List<Employee>() {new Employee()}); // assert Assert.AreEqual(result, 1); }
3. Users with the user role have the Get
functionality access (the same for the admin role).
[Test] public void CheckThatUserHaveGetAccess() { // init Thread.CurrentPrincipal = new Principal("User"); // action var result = employeeRepository.Get(); // assert Assert.AreNotEqual(result.Count, 0); }
The performed unit tests work for each type of the repositories mentioned above.
How Unity can help
With cross-cut functionality injection, Unity provides possibility to intercept method calls by using call handlers (e.g., the ICallHandler
interface). This interface includes the invoke
method, which contains required functionality that should be executed before actual method execution. In our example, it will be permission to check functionality. In addition, this interface defines the Orders
property, which should return the call handler order number. This is useful in case of using several call handlers (we can specify the order of their execution).
After having our custom call handlers implemented, we should specify when and where they should be executed. Unity allows for several ways to do it. With the attribute approach, we should mark methods with custom attributes inherited from HandlerAttribute
. These attributes are responsible for appropriate call handler creation by overriding the CreateHandler
factory method.
Step 1
First of all, we will create a call handler that should contain permission check logic.
public class MethodAccessCallHandler : ICallHandler { private readonly string[] roles; public MethodAccessCallHandler(params string[] roles) { this.roles = roles; } public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { IPrincipal principal = Thread.CurrentPrincipal; bool allowed = roles.Any(principal.IsInRole); if (!allowed) return input.CreateExceptionMethodReturn(new Exceptions.MethodAccessSecurityException()); return getNext()(input, getNext); } public int Order { get; set; } }
Our call handler throws MethodAccessSecurityException
, a custom exception inherited from ApplicationException
, in case of a permission check failure.
Step 2
After that, we will create a custom attribute inherited from HandlerAttribute
that should override the CreateHandler
factory method for the appropriate call handler creation (see Step 1).
public class MethodAccessAttribute : HandlerAttribute { private readonly string[] roles; public MethodAccessAttribute(params string[] roles) { this.roles = roles; } public override ICallHandler CreateHandler(IUnityContainer container) { return newMethodAccessCallHandler(roles); } }
Step 3
Then, we will mark the IRepository interface methods (permission to which should be controlled) with the attribute created during Step 2.
public interface IRepository { [MethodAccess(Roles.AdminRole, Roles.UserRole)] IList<T> Get(); [MethodAccess(Roles.AdminRole)] int Save(IList<T> entities); [MethodAccess(Roles.AdminRole)] int Delete(IList<Guid> ids); }
By marking these methods, we actually add permission check functionality to all classes that will implement this interface. Now, you can run unit tests.
Unity allows us to inject cross-cutting functionality with minimal efforts across such areas as domain object changes tracking, automatic PropertyChanged
event rising for all domain object properties, etc.
About the author
Further reading
- How to Run Capybara Tests for Ruby Applications in Remote Browsers
- Speeding up Ruby Tests
- Linq-To-Sql: Alternative to the ‘WHERE IN’ Expression
This blog post was written by Aliaksei Yenzhyieuski; edited by Olga Belokurdkaya and Alex Khizhniak.