Originally this post was going to be titled "Part 2: Integrating Rhino Security" but upon reflection this wasn't what I was trying to acheive at all. This has been covered enough to get you started and if you need to know how to integrate Rhino Security into your ASP.NET WebForms project then read this post by Billy McCafferty. What I do want to discuss is the concept of Rhino Security, its simplicity, adaptability and extensibility and why it is "the shit".
Brief Overview Of Rhino Security Architecture
Rhino security is an authorization framework that is easily integrated into existing .Net applications to provide a security framework that covers a wide range of authorization scenarios from the simple to the complex. In essence, this boils down to permitting or denying a user, or group of users, from carrying out an operation.
Ayende implements operations using the following notation:
The major benefit of this implementation is that the interpretation of the operations is completely up to the developer. I'll give you an example to show you what I mean. In part 1 I outlined that in our application there is a requirement to implement page level permissions. In order to implement this (I will explain the actual page level permissions implementation in the next post) I prefix all page level operations with '/Page/' and append this with the URL of that given page, such as:
You can start to see that the operations format gives a huge amount of flexibility and scope for the developer to implement page level, object level and feature level permissions without dictating implementation.
Part 1 also described the requirement that permissions must be based on both users and groups. Rhino Security supports this notion by associating permissions with both users and users groups (see schema below).
Finally Rhino Security has the concept of permission levels. It isn't completely clear from the documentation as to the intended use of the level, although it is clear that higher level override permissions of a lower level. However, I see this as a massive benefit as Ayende leaves the thinking and implementation up to the developer. For example in my implementation I give inherited user groups a higher level than their parent. So given the scenario where you may have a 'Developer' role (users group) which would have associated permissions. You may have another 'Senior Developer' role that is a child of the 'Developer' role. In my implementation the 'Senior Developer' permissions set should have a greater weighting than it's parent as it is more specific. The 'Developer' would therefore have a level of 1 and the 'Senior Developer' will have a level of 2. Again...just to reiterate this is simply my implementation and by no means a restriction of the framework.
Authorization with Rhino Security
Rather than describe the Repositories and Services to get started with Rhino Security as they are better described here, I will run you through a use case and describe the process.
Say, for example, that we have two users, one is called Bill and he is a Senior Developer and another user called Tom and he is a Junior Developer. Our application must only allow Senior Developers to add new companies to the system.
Given that Rhino Security is integrated into our system, whereby we already have a User table to which Bill and Tom are registered in, the first thing we must do is create our UsersGroups. The way I'm going to do this is to create a parent UsersGroup for Developers, to which Junior and Senior developer roles will inherit from.
_authRepository = ResolveType.Of<IAuthorizationRepository>();
_authRepository.CreateChildUserGroupOf("Developer", "Junior Developer");
_authRepository.CreateChildUserGroupOf("Developer", "Senior Developer");
Now that we have created the UsersGroups we can add both Bill and Tom to the relevant roles:
IUser userA = _userRepository.GetUser("Bill");
IUser userB = _userRepository.GetUser("Tom");
_authRepository.AssociateUserWith(userA, "Senior Developer");
_authRepository.AssociateUserWith(userB, "Junior Developer");
We can now create the operations that we wish to be authorized against. In this use case we are wanting to restrict access at an object level and will therefore use the convention of /ObjectName/Operation. Therefore we will use /Company/Add.
The IPermissionsBuilderService tie's all of this together to associate the UsersGroups with a particular operation and at a specified level. As you can see from the example below this is implemented using a fluent interface.
private void GrantPermission(string operation, bool allowed, UsersGroup usersgroup)
GrantPermission("/Company/Add", false, _authRepository.GetUsersGroupByName("Developer"));
GrantPermission("/Company/Add", true, _authRepository.GetUsersGroupByName("Senior Developer"));
If you are wondering what the GroupLevel() method is or does, this is not part of the UsersGroup object out-of-the-box, I wrote this extension method to implement permission levels, so that all permissions are registered above those of their parents. In our use case, the Senior Developer role is a child group of the Developer UsersGroup and as a result the Senior Developer permissions will be at a level above it's parent. This may make more sense if I show you the extension method.
public static int GroupLevel(this UsersGroup usersGroup)
return usersGroup.AllParents.Count + 1;
At this point we pretty much have our use case covered. In summary, Bill and Tom are Developers, Bill is a Senior Dev and Tom a Junior. Bill, as a Senior Dev, is permitted to add companies to the application, Tom as a Junior Dev is not permitted as his parent role prevents him from doing so.
Given that the plumbing is now sorted we can enforce authorization using the bool IAuthorizationService.IsAllowed(IUser user, string operation) method.
// Returns true
bool canBillAddCompany = _authRepository.IsAllowed(userA, "/Company/Add");
// Returns false
bool canTomAddCompany = _authRepository.IsAllowed(userB, "/Company/Add");
In reality what I have covered here will be hidden behind some kind of UI or service. This will be covered in later posts. In my next post I will be detailing how to implement a dynamic permissions discovery framework on top of Rhino Security to automate the registration of permission.