(And yes, I did mean PlugIns – darned FXCop rules)

After a couple of weeks of experimentation with code I think I have the plug-in model complete now. As suggested by Travis in the comments on a previous post as many parameters as possible are now using System.Web.Abstractions. Right now there are three main interfaces:

/// <summary>
/// Defines methods that must be implemented for request inspection.
/// </summary>
public interface IRequestInspector : ISecurityRuntimePlugIn
{
    /// <summary>
    /// Inspects an HTTP request for potential problems.
    /// </summary>
    /// <param name="request">The <see cref="HttpRequestBase"/> to inspect.</param>
    /// <returns>An <see cref="IInspectionResult"/> containing the results of the inspection.</returns>
    IInspectionResult Inspect(HttpRequestBase request);
}
/// <summary>
/// Defines methods that must be implemented for response header inspection.
/// </summary>
public interface IResponseHeaderInspector : ISecurityRuntimePlugIn
{
    /// <summary>
    /// Inspects an HTTP response's headers for potential problems.
    /// </summary>
    /// <param name="request">The original <see cref="HttpRequestBase"/> for this response.</param>
    /// <param name="response">A <see cref="HttpResponseBase"/> to inspect.</param>
    /// <returns>An <see cref="IInspectionResult"/> containing the results of the inspection.</returns>
    /// <remarks>
    /// <para>This method is called at the point in the ASP.NET pipeline where you may still edit response headers
    /// via <see cref="HttpResponse.AppendHeader"/>.</para>
    /// <para>Accessing the <see cref="HttpResponse.Headers"/> property is only supported with the IIS 7.0 integrated pipeline mode 
    /// and at least the .NET Framework 3.0. When you try to access the Headers property and either of these two conditions 
    /// is not met, a PlatformNotSupportedException is thrown.</para>
    /// </remarks>
    IInspectionResult Inspect(HttpRequestBase request, HttpResponseBase response);
}
/// <summary>
/// Defines methods that must be implemented for page inspection.
/// </summary>
public interface IPageInspector : ISecurityRuntimePlugIn
{
    /// <summary>
    /// Inspects an HTTP page for potential problems.
    /// </summary>
    /// <param name="page">The <see cref="Page"/> to inspect.</param>
    /// <returns>An <see cref="IInspectionResult"/> containing the results of the inspection.</returns>
    IInspectionResult Inspect(Page page);
}

You can also see these interfaces also rely on ISecurityRuntimePlugin, which looks like this:

/// <summary>
/// Defines methods and properties that must be implemented for any plugin to the Security Runtime engine.
/// </summary>
public interface ISecurityRuntimePlugIn
{
    /// <summary>
    /// Gets the list of excluded paths for the plugin.
    /// </summary>
    /// <value>
    /// The list of excluded paths for the plugin.
    /// </value>
    ExcludedPathCollection ExcludedPaths
    {
        get;
    }
}

Each and every plug-in must support an exclusion method for paths. Now I’m aware that rewriters complicate things – attribute based exclusion is not going away but path based exclusion will allow you to wrap the SRE and any plug-ins around sites for which you do not have the source for or do not have the time to edit the source to exclude by attributes.

You may notice there’s no raw inspectors for requests or responses – they’re in my list of things to do, but as none of the customer requests need them right now they’ll probably turn up in the next sprint. One of the feature requests for this sprint was ClickJack protection, by always adding the X-FRAME-OPTIONS header to every response. So this becomes rather easy to implement – I had bet Frank my PM I could do it in a couple of lines of code. It took about 5 lines because of the excluded path property. Darnit. The code for the simplest version of this looks something like

[Export(typeof(IResponseHeaderInspector))]
public sealed class ClickJackResponseHeaderInspector : IResponseHeaderInspector
{ public ExcludedPathCollection ExcludedPaths { get { return null; } } public IInspectionResult Inspect(HttpRequestBase request, HttpResponseBase response) { if (response != null) { response.AppendHeader("X-FRAME-OPTIONS", "DENY"); } return new ResponseInspectionResult(InspectionResultSeverity.Continue); } }

There’s none of the usual hoops to jump through to write an HttpModule because you’re not writing one – the SRE is the module and will take of running your code within the appropriate ASP.NET pipeline events, which is why there’s that interesting [Export] attribute - the WPL plug-in functionality is provided by the Managed Extensibility Framework with is part of .NET 4.0 but is also available on CodePlex for .NET 3.5. Given there’s so little code to write (for something this simple anyway) your plug-ins should be very testable (and I’m planning to publish the unit tests for the SRE when we actually release). Of course a simple example should as the ClickJack inspector is unrealistic. Really you would want it to be configurable, after all you can set the header to one of two values – so in reality it’s a little bigger as there is another interface to implement, IConfigurablePlugIn (the name may change):

/// <summary>
/// Defines methods and properties that must be implemented for plugin configuration.
/// </summary>
public interface IConfigurablePlugIn
{
    /// <summary>
    /// Gets the configuration section name for the plugin.
    /// </summary>
    string ConfigurationSectionName
    {
        get;
    }

    /// <summary>
    /// Configures the current instance of the plugin.
    /// </summary>
    /// <param name="configurationSection">The configuration section for the module.</param>
    void Configure(ConfigurationSection configurationSection);
}

This interface requires you to expose a section name for your configuration section and will then extract that section during the SRE initialization and pass it down to your plug-in to do with as you please. This approach lends itself to more testability instead of the usual approach to writing custom configuration sections where you assume the section name exists and hard code it into your ConfigurationSettings class. You may be wondering why I didn’t put the ExcludedPaths property in that interface – there was a couple of reasons, 1) I wanted to enforce its availability for all plug-ins and 2) you may not want to source the path exclusions from a configuration file, you may want them from somewhere else, for example a SQL database. A settings class for the ClickJack inspector looks as follows

internal sealed class ClickJackInspectorSettings : BasePlugInConfiguration
{
    /// <summary>
    /// The property name for the plugin directory attribute.
    /// </summary>
    private const string HeaderValueProperty = "headerValue";

    /// <summary>
    /// An instance of the internal settings.
    /// </summary>
    private static ClickJackInspectorSettings internalSettings;

    /// <summary>
    /// Gets the settings.
    /// </summary>
    /// <value>The configuration settings settings.</value>
    public static ClickJackInspectorSettings Settings
    {
        get
        {
            return internalSettings ?? (internalSettings = new ClickJackInspectorSettings());
        }
    }

    /// <summary>
    /// Gets the header value to insert.
    /// </summary>
    /// <value>The header value to insert.</value>
    [ConfigurationProperty(HeaderValueProperty, IsRequired = false, DefaultValue = ClickJackHeaderValue.Deny)]
    public ClickJackHeaderValue HeaderValue
    {
        get
        {
            return (ClickJackHeaderValue)this[HeaderValueProperty];
        }
    }

    /// <summary>
    /// Gets the excluded paths for the plugin.
    /// </summary>
    /// <value>The excluded paths for the plugin.</value>
    [ConfigurationProperty(DefaultExcludedPathsProperty, IsDefaultCollection = true, IsRequired = false)]
    public override ExcludedPathCollection ExcludedPaths
    {
        get
        {
            return this[DefaultExcludedPathsProperty] as ExcludedPathCollection;
        }
    }

    /// <summary>
    /// Configures from configuration section.
    /// </summary>
    /// <param name="configurationSection">The configuration section.</param>
    public override void ConfigureFromConfigurationSection(ConfigurationSection configurationSection)
    {
        internalSettings = configurationSection as ClickJackInspectorSettings;
    }
}

You can see there’s a base class available, BasePlugInConfiguration – this provides ConfigureFromConfigurationSection, which is called for IConfigurablePlugins, and helps provides DefaultExcludedPathsProperty, which is the same string literal used for the SRE configuration excluded paths so you can use the same configuration syntax if you wish. So now our configurable plug-in looks like this

[Export(typeof(IResponseHeaderInspector))]
public sealed class ClickJackResponseHeaderInspector : IResponseHeaderInspector, IConfigurablePlugIn
{
    private const string ConfigSectionName = "sreClickJackPluginSettings";

    public ExcludedPathCollection ExcludedPaths
    {
        get
        {
            return ClickJackInspectorSettings.Settings.ExcludedPaths;
        }
    }

    public string ConfigurationSectionName
    {
        get
        {
            return ConfigSectionName;
        }
    }

    public IInspectionResult Inspect(HttpRequestBase request, HttpResponseBase response)
    {
        if (response != null)
        {
            response.AppendHeader("X-FRAME-OPTIONS", ClickJackInspectorSettings.Settings.HeaderValue == ClickJackHeaderValue.SameOrigin ? "SAMEORIGIN" : "DENY");
        }

        return new ResponseInspectionResult(InspectionResultSeverity.Continue);
    }

    public void Configure(ConfigurationSection configurationSection)
    {
        if (configurationSection != null)
        {
            ClickJackInspectorSettings settings = new ClickJackInspectorSettings();
            settings.ConfigureFromConfigurationSection(configurationSection);
        }
    }
}

That’s not too bad – if only I could have static properties or methods on interfaces or abstract classes I could reduce the code even further. Oh and I’d like a pony. Hopefully you can see where I’m going with this, I know at least one MVP is champing at the bit to write his own plugin. We’re deciding when/how to let you have a look at this – I’m leaning towards pushing the code onto CodePlex at the end of the sprint so you can download and play, but I may hold off a binary release until we think we’re good to go. As usual all thoughts/feedback welcome and all of the above may change drastically – but I really hope not.