This project provides Role Based Access Control (RBAC) to allow easier authorization of activities in your system and also provides support for this in ASP.NET MVC and WebAPI projects.
It is available as a set of NuGet packages Meerkat.Security, Meerkat.Security.Mvc and Meerkat.Security.WebApi
Welcome to contributions from anyone.
You can see the version history here.
The tooling should be automatically installed by paket/Fake. The default build will compile and test the project, and also produce the nuget packages.
The library is available under the MIT License, for more information see the License file in the GitHub repository.
Security is hard, and the goal of this project is to make it a bit easier to get a good level of default security with low effort into your project. Having said that, your requirements will be different from mine so make sure that you first understand what you are trying to do and that this project is a good fit.
Here we cover off the key building blocks of the framework
There are four classic models of access control:
This framework is most closely aligned to RBAC but has some ABAC capabilities as well, e.g. you can secure an operation based on additional attributes about the subject and also use hierarchies to implement more fine-grained permissions.
The main advantage of the framework is its ease of implementation and speed since operations will take place in memory, and this leads us to its main limitation in that it is intended to secure activities on the class of the resource rather than individual resources i.e. we can easily secure Order.Create or Order.Edit to a role SalesClerk but restricting editing of Order 231 for example would require domain knowledge and/or database access to know who can edit this particular order and so it out of scope.
Most developers are aware that .NET processes execute under a user context called a Principal, this is easily accessed via Thread.CurrentPrincipal or in a web application via the controllers User property. Since .NET 4.5 these are all ClaimsPrincipals which can contain multiple ClaimsIdentities each holding Claims. A claim is a security attribute with some basic properties...
In this model, roles and the user's name are not anything special, just particular types of claim.
How we arrive at these claims is out of scope for this project but in general they are the responsibility of the authentication system where users, groups and roles are allocated and can also be further enhanced by a process called claims transformation which happens as part of the ASP.NET pipeline.
A lot of examples of securing .NET applications show security against activities such as CreateUser and UpdateUser and one issue I have with this is that the number of securable items can grow very rapidly e.g. in a fairly small system of 20 resource types with just CRUD operations you have 80 securable items and this quite rapidly becomes difficult to manage. This project took a slightly different perspective akin to how REST models things and explicitly separated the resource from the activity that is being secured.
This allows you to define granular but wide-ranging permissions e.g. grant Delete permission to the Admin role on all resources or grant all permissions on Order to the SalesClerk role.
One concept that simplifies security maintenance is the concept of Deny permissions which take precedence over any Grant. For example, your normal sales clerk Alice is off for the say so you grant Bob the SalesClerk role, but you don't want him to be able to do too much damage so you also say Bob Deny Order.Delete, now this Deny permission for Bob will take precedence over the SalesClerk's standard permissions to be able to do anything to an Order.
Following on from the idea of resources and activities, your security model should be focused on your business functionality rather than how you map this to a particular software implementation. If you do this, it will be much easier to explain to the business sponsors what is being secured and why, and should more naturally flow from any analysis discussions e.g. "We only want managers to be able to approve invoices".
Another way of developing this structure is rather than resource/activity is noun/verb; nouns represent objects that need securing and verbs are the operations that users will perform. You can stray from this pattern, but it should be intentional rather than accidental.
Lets take a simple business model
So what we want to implement is the following
This gives a reasonable separation of powers where it requires at least two actors to colude to create an order, raise an invoice and get it approved.
This can be represented in the following table, blank nodes means no rights
Resource | Activity | SC | SM | IC | FM | FD |
---|---|---|---|---|---|---|
Order | Read | x | x | x | x | |
Order | Create | x | ||||
Order | Edit | x | ||||
Order | Ship | x | x | |||
Order | Cancel | x | x | |||
Order | Delete | x | ||||
Invoice | Read | x | x | x | x | |
Invoice | Create | x | ||||
Invoice | Edit | x | ||||
Invoice | Approve | x | ||||
Invoice | Cancel | x | ||||
Invoice | Delete | x |
We can define the model in either XML or JSON according to preference, here's the XML representation
<financeAuthorization name="Finance" allowUnauthenticated="false">
<activity name="Order.Read">
<allow roles="SalesManager, FinanceManager, FinanceDirector" />
</activity>
<activity name="Order">
<allow roles="SalesClerk" />
</activity>
<activity name="Order.Ship">
<allow roles="SalesManager" />
</activity>
<activity name="Order.Cancel">
<allow roles="SalesManager" />
</activity>
<activity name="Order.Delete">
<allow roles="FinanceDirector" />
<deny roles="SalesClerk" />
</activity>
<activity name="Invoice.Read">
<allow roles="SalesManager, FinanceManager, FinanceDirector" />
</activity>
<activity name="Invoice">
<allow roles="InvoiceClerk" />
</activity>
<activity name="Invoice.Approve">
<deny roles="InvoiceClerk" />
<allow roles="FinanceManager" />
</activity>
<activity name="Invoice.Cancel">
<deny roles="InvoiceClerk" />
<allow roles="FinanceManager" />
</activity>
<activity name="Invoice.Delete">
<deny roles="InvoiceClerk" />
<approve roles="FinanceDirector" />
</activity>
</financeAuthorization>
It does not matter what order the activities are defined in, once they are loaded into memory we produce a canoncial ordering so we always process from most specific to least specific, see hiearchies for more details.
This allow us to define wider rules and then pare them back with exclusions e.g.
<activity name="Invoice">
<allow roles="InvoiceClerk" />
</activity>
<activity name="Invoice.Approve">
<deny roles="InvoiceClerk" />
<allow roles="FinanceManager" />
</activity>
This grants all activities on Invoice to the InvoiceClerk, but then specifically disallows them from Approve whilst granting that right to the FinanceManager.
One general principle is not to have hierarchies of roles, if you do so it makes it much more difficult to see the implications of changes to the security model and can lead to inadvertent breaches of separation of concern rules. For example, if instead of explicitly denoting a FinanceDirector's permissions we also said that they also inherit the other roles, then a bad actor could create an order, invoice it and approve it without the security model complaining.
Sometimes you need more fine-grained security, for example you want to provide some property-level permissioning so employee information is available but their salary information is not. Another example might be that you want people to being to print some reports but exclude them from more sensitive data; to do this we have the concept of both resource and activity hierarchies.
This is achieved by using a path separator "/" in either the resource, the activity or both, the authorization system then checks them using a canonical ordering from most specific to least specific to determine the permission to apply, and this first one to make a decision is the one applied. For example given the following security fragment...
<activity name="Reports.Print" authorized="true" />
<activity name="Reports/Sales.Print" authorized="false">
<allow roles="Sales" />
</activity>
<activity name="Reports/Employees.Print" authorized="false">
<allow roles="HR" />
</activity>
This says that all authenticated users are allow to print reports, but only Sales are allowed to print Sales reports and only HR are allowed to print Employee reports.
So if we ask to authorize Reports/Sales.Print the following activities would be considered in this order
Reports/Sales.Print -> Reports.Print -> .Print
By using 'authorized="false"' at the sublevels we are forcing the decision to be evaluated there, otherwise it would bubble up to Reports.Print. The other alternative is to use Deny permissions but this can get tricky as a Deny is an immediate block on further evaluation, e.g. given
<activity name="Reports.Print">
<allow roles="Users" />
</activity>
<activity name="Reports/Sales.Print" authorized="false">
<deny roles="Users" />
<allow roles="Sales" />
</activity>
<activity name="Reports/Employees.Print" authorized="false">
<deny roles="Users" />
<allow roles="HR" />
</activity>
The problem is if you make a HR user a member of Users, they would be locked out of their reports due to the Deny taking precedence. This also brings up a good point of generally avoiding groups such as Users. We effectively get this overall group by virtue of users being authenticated and you can include everyone with for an activity with a 'authorized="true"' clause or even 'allowUnauthenticated="true"', for example one standard entry I have in most security files is
<activity name="Home.Index" authorized="true" allowUnauthenticated="true" />
To use the hierarchies effectively you might need to make more than one call to the authorization system...
To secure application we need to:
Some of this code is generic and is in the Meerkat.Security assembly, but the MVC specific code is held in Meerkat.Security.Mvc and this should be referenced from your MVC project. The process to secure a WebApi application is the same as that for an MVC application, but a different assembly must be referenced as MVC and WebApi do not share the same code base.
We want the security model to be enforced with as little programmer intervention as possible, so the easy way is to introduce a custom attribute similar to the built-in Authorize attribute. Our one is called ActivityAuthorize and can either be registered via a global filter, or explicitly placed on a controller. If introduced as a global filter, the system automatically determines the relevant controller/action information from the routing information.
It is possible to put multiple ActivityAuthorize on a controller action but be aware that all such attributes must evaluate to true for the user to be granted access; this may be useful where more complex security requirements arise.
There is a fairly natural mapping from controller/action to resource/activity but it sometimes needs a little help so that we do not unnecessarily multiple up activities. For a standard MVC controller you will typically have the following actions, the relevant activity is shown afterwards
For a WebAPI controller we have a slightly different set if we are following REST API rules
If you are using an OData controller you may also need to support sub-types of your main entities
To avoid having to specify the changed values on every controller, we introduce a new service IControllerActivityMapper with a default implementation that performs the basic mappings shown above.