This is the part 3 of a series of posts on Rhino Security, which is an enterprise security framework built on top of NHibernate, by Ayende Rahien. In part 2 I looked at how Rhino Security works. Here I’ll look at getting it up and running. Again I’ll be referencing back to points I made in previous parts so I’m assuming you’ve read and understood them.
First up lets get some binaries. You can download the latest build here.
If you interested compiling from source you can grab it here. For the Mono chaps, I’m afraid the the Rhino Security build scripts are psake, which is an automation tool for Powershell, a Windows command line shell . There is a cross platform implementation of Powershell out in the wild but with limited functionality, so moving forwards its either writing a nant build script or investigating the aforementioned PASH.
We also need to grab the Common Service Locator as Rhino Security depends on this. Previous versions of Rhino Security used to depend on Castle Windsor but it can now be used with any IOC container, as it now depends on the CSL which is essentially an interface for IOC containers, more on this in a moment.
Essentially we need to do 7 things before we can start using Rhino Security on our project:
- Register several Rhino Security services on our container so that our application can use them.
- Configure our IOC container so when Rhino Security requests something which implements an ISession, the session is returned to it.
- Configure the CSL to point to Windsor.
- Augment our NHibernate configuration data with some Rhino Security table information.
- Add some security keys on our entities.
- Implement IUser on our user entity.
- Add an entity information extractor so that Rhino Security understands our domain sufficiently.
Now to my specific setup, I’m using Castle Windsor as my IOC container, binsor to configure it, and the Castle NHibernate Facility. Rhino Security used to come with a facility you could simply reference in your config but now that it no longer depends on Windsor we need to write a custom facility (or extension point depending on your choice of container/preferred language) to achieve our first 2 objectives. Using Windsor, my facillity looks like this
public class RhinoSecurityFacility : AbstractFacility
{
protected override void Init ()
{
Kernel.Register (Component.For<IAuthorizationService> ()
.ImplementedBy<AuthorizationService> ()
.LifeStyle.PerWebRequest,
Component.For<IAuthorizationRepository> ()
.ImplementedBy<AuthorizationRepository> ()
.LifeStyle.PerWebRequest,
Component.For<IPermissionsBuilderService> ()
.ImplementedBy<PermissionsBuilderService> ()
.LifeStyle.PerWebRequest,
Component.For<IPermissionsService> ()
.ImplementedBy<PermissionsService> ()
.LifeStyle.PerWebRequest);
Kernel.Resolver.AddSubResolver (new SessionResolver ());
}
}
I start by registering Rhino Security services on the container. These services are transient to ensure that any call to that service grabs the current session. I then add a Session Resolver. There are several ways of configuring the session, you can use a factory method or make use of an ISubDependencyResolver, which is the method I have chose. The main reason for this is that the factory method implements IDisposable and can lead to memory leaks, though there are workarounds for this. Also this method would allow you to use multiple dbs, which you couldn’t achieve with the factory method.
Here is my Session Resolver
using Castle.Core;
using Castle.Facilities.NHibernateIntegration;
using Castle.MicroKernel;
using NHibernate;
public class SessionResolver : ISubDependencyResolver
{
IKernel Kernel { get; set; }
public object Resolve (CreationContext context,
ISubDependencyResolver contextHandlerResolver,
ComponentModel model,
DependencyModel dependency)
{
return Kernel.Resolve<ISessionManager> ().OpenSession ();
}
public bool CanResolve (CreationContext context,
ISubDependencyResolver contextHandlerResolver,
ComponentModel model,
DependencyModel dependency)
{
return typeof(ISession).IsAssignableFrom (dependency.TargetType);
}
}
You notice that I am resolving the ISessionManager, this is something the NH facility provides me which manages the session. Essentially in here you need whatever manages your Unit of Work to return the current session.
Ok, first 2 down. Now step 3. I need to add a Windsor adaptor for the CSL, again written by Ayende Rahien
using System;
using System.Collections.Generic;
using Castle.Windsor;
using Microsoft.Practices.ServiceLocation;
public class WindsorServiceLocator : ServiceLocatorImplBase
{
private readonly IWindsorContainer container;
public WindsorServiceLocator (IWindsorContainer container)
{
this.container = container;
}
protected override object DoGetInstance (Type serviceType, string key)
{
if (key != null)
return container.Resolve (key, serviceType);
return container.Resolve (serviceType);
}
protected override IEnumerable<object> DoGetAllInstances (Type serviceType)
{
return (object[])container.ResolveAll (serviceType);
}
}
And now to tell the Service Locator to use Windsor, I add this line where I have access to my container, as I’m dealing with a web app I add it in my Application_OnStart()
ServiceLocator.SetLocatorProvider (() => new WindsorServiceLocator (_container));
Step 4 can be achieved by a static method Rhino Security makes available to us.
Security.Configure<Person> (configuration, SecurityTableStructure.Prefix);
The Castle NHibernate facility allows you to modify the NHibernate configuration as its being constructed. Within my binsor NHibernate facility configuration I make a call to my specific configuration builder class, this implements IConfigurationBuilder, an interface provided by the Castle NHibernate facility. The method GetConfiguration accepts my facility configuration which I can then augment using the method above and then return. You can read more about how the facility works here. If your not using the Castle NHibernate Facility, at the point your Unit of Work code registers the ISessionFactory on the container you’ll want to augment your NHibernate config using the above code.
Step 5 and 6 are more time consuming than anything else, simply implement the IUser interface on your user entity and add a GUID security key on EVERY entity :)
Step 7, requires the addition of an entity information extractor, this is a class Rhino uses to understand the parts of our domain it needs to. Firstly Rhino Security needs to understand which property of your entity holds the SecurityKey. Secondly it needs to understand how to look up entities based on the key and return some meaningful information about it.
You can either write one for each entity or write a generic one, heres my generic one
public class EntityInformationExtractor<TEntity> : IEntityInformationExtractor<TEntity>
where TEntity : Entity
{
private ISessionManager sessionManager;
public EntityInformationExtractor (ISessionManager SessionManager)
{
sessionManager = SessionManager;
}
public Guid GetSecurityKeyFor (TEntity entity)
{
return entity.SecurityKey;
}
public string SecurityKeyPropertyName {
get { return "SecurityKey"; }
}
public string GetDescription (Guid SecurityKey)
{
TEntity entity;
var criteria = DetachedCriteria.For<TEntity> ().Add (Expression.Eq (SecurityKeyPropertyName(), SecurityKey));
using (ISession session = sessionManager.OpenSession ()) {
entity = criteria.GetExecutableCriteria (session).UniqueResult<TEntity> ();
}
return string.Format ("Event: {0}", entity.Name);
}
}
Method 1 and 2 deal with accessing the property which contains the Security Key, firstly when Rhino Security has the entity and secondly when it doesn’t. Method 3 reports on the entity in question. I just choose to return the entities name. I can do this generically as I have made my entire domain inherit from a class called entity which has the properties Id, Name and SecurityKey, hence why TEntity : Entity in this class.
Thats it. Configure your facility/extension on your container, add some Rhino Security tables in your database and you should be ready to start writing code!
I realise this may not all immediately make sense but it is worth having a good grounding on the principles of Rhino Security, before writing code.
Next post
Part 4: Using Rhino Security