Extensions
A lot of my work in building security for CruiseControl.Net has been in making it extendable. Rather than forcing people to use only one form of authentication, or authorisation or caching, I’ve built a framework that people can add their own extensions in to (very similar to the underlying principal of CruiseControl.Net in general). Now is the time to put this to the test and try and build a couple of extensions.
One common scenario for security is in a local security domain that uses Active Directory (AD). It would be nice to allow this rather than forcing people to use another user name/password. So I’m going to add an authentication process that uses active directory.
Secondly, I don’t like the way sessions are lost when the server is restarted (e.g. when the configuration changes or the service does a restart). It would be nice to have a more persistant session cache that handles restarts. Since the cache management is also extendable, I’m going to add an implementation of a file-based session cache.
AD Authentication
An authentication process needs to implement IAuthentication, which also includes ISecuritySetting. ISecuritySetting returns an identifier which is used by the security manager for storing the authentication. IAuthentication contains a method for authenticating, plus methods for returning the user and display names. Additionally, since these are loaded by Exortech.NetReflector the class must have the relevant attributes to allow Exortech.NetReflector to load it.
I’ve added a new class called ActiveDirectoryAuthentication that implements IAuthentication. This has a ReflectorType attribute of “ldapUser”, and a ReflectorProperty decorated property called UserName. This property will be where either the AD name or a wild card will be stored. I also added another decorated property called DomainName for storing the LDAP domain to use – this is also a required property.
Now the fun part comes with the Authenticate() method. This will be responsible for checking that the credentials are actually valid. To handle finding a user account, I’ve added a private method called FindUser(). This uses LDAP to search for a user, and if found returns the user name. The Authenticate() method uses this to find a display name. If there is a display name, then the account is valid, otherwise authentication has failed.
The GetUserName() method merely returns the name from the credentials, while the GetDisplayName() uses the FindUser() method to find the user. If a user is found, then the name is returned, otherwise the user name is returned instead.
And that’s all I’m doing for the AD authentication. Later I may look at adding password validation as well, but I’m not too keen on sending a password across an open network (I should also look into securing the transport mechanism as well).
File-Based Sessions
Session caching is exposed via the ISessionCache interface. This is a bit more complex than authenticate as it needs to store, remove and retrieve sessions, store and retrieve values against a session, plus it allows for an initialisation process. Now, I could write an entire session cache from scratch, but I’m going to cheat and base my cache on the InMemorySessionCache. To do this, I’m going to do some refactoring first and split the base functionality of InMemorySessionCache into an abstract base class and then I’ll implement my file-based session cache from this base class.
The reason for this is I’m not too sure on how Exortech.NetReflector will handle a type that inherits from another reflector type – so I’m going to play it safe. For my refactoring all I did was move al the functionality into its own class, made all the private entries protected and made sure all the public entries were virtual. The new implementation of InMemorySessionCache is just an empty class that implements my new abstract base class, plus it contains the RelectorType attribute (since all the memory cache functionality is in the base class).
Now for the fun part – I’ve added a new class called FileBasedSessionCache. This is also derived from SessionCacheBase, but it also needs to persist data to the file system. I’m going to use the basic memory-based caching so the file system isn’t getting overworked, but I also need to update the file system whenever new details are set. So, the methods that update the file system are:
- AddToCache()
- RemoveFromCache()
- StoreSessionValue()
I’m also going to override Initialise() to load all the data from the file system.
For saving session details I’ve added a SaveSession() method. This will persist all the session details into an XML file. There will be one file per session – this is for performance reasons (a single file would mean reading, parsing and writing a single file every update). I’ve added a new property, StoreLocation, to store where these files will be stored. By default this is a folder under the current directory called sessions. This is exposed via Exortech.NetReflector so the administrator can change it if required.
All actually saving of the file is done by a helper method called SaveSession(). This does the following:
- Generate the file name (by another helper method – GenerateFileName())
- Check if the file exists
- If the file exists, load the file and parse it into an XmlDocument
- If the file doesn’t exist, start a new XmlDocument, add a root node and store the session token
- Store the user name in the document
- Add all the values
- Save the document
AddToCache() and StoreSessionValue() merely call the base methods, and then the SaveSession() method. RemoveFromCache() again calls the base method, but instead of saving it attempts to find the file and delete it.
The only outstanding method is Initialise(). This first checks fr the existance of the directory. If it doesn’t exist then the directory is created. Otherwise it lists all the directory files and attempts to load them. This involves loading the session into an XmlDocument, extracting all the relevant values, reconsituting the session entity and adding it to the base cache. Simple
End of Changes
That’s the end of the changes – the security framework has now been extended to handle a new authentication mode and a new cache mode. Hopefully this shows how easy it is to add new extensions to the security.