Monday, May 28, 2012

Using NUnit’s Parametrized Tests to test different implementations of a service registered in an IoC container

There is a service:

public interface IFoo
{
    int DoFoo();
}

and at least two different implementations of it:

public class Bar : IFoo
{
    #region IFoo Members
 
    public int DoFoo()
    {
        return 1;
    }
 
    #endregion
}
 
public class Qux : IFoo
{
    #region IFoo Members
 
    public int DoFoo()
    {
         return 2;
    }
 
    #endregion
}

We often use an IoC container to bind the implementation to the abstraction, some of us even use a service locator to resolve services at runtime:

IUnityContainer container = new UnityContainer();
container.RegisterType<IFoo, Bar>();
 
UnityServiceLocator locator = new UnityServiceLocator( container );
ServiceLocator.SetLocatorProvider( () => locator );

Having at least two different implementations, I would like to be able to test them against some common criteria like “does a method always returns values”.

First approach – inheritance on test classes

My first approach to test different implementations was to create a base abstract test class and inherit it everytime I need to test another implementation:

public abstract class TestBase
{
    [SetUp]
    public virtual void SetUp()
    {
        IUnityContainer   container = new UnityContainer();
        UnityServiceLocator locator = new UnityServiceLocator( container );
 
        ServiceLocator.SetLocatorProvider( () => locator );
    }
 
    [Test]
    public void Foo()
    {
        var foo = ServiceLocator.Current.GetInstance<IUnityContainer>().Resolve<IFoo>();
 
        Assert.That( foo.DoFoo(), Is.GreaterThan( 0 ) );
    }
}
 
[TestFixture]
public class BarTest : TestBase
{
    public override void SetUp()
    {
        base.SetUp();
 
        ServiceLocator.Current.GetInstance<IUnityContainer>().RegisterType<IFoo, Bar>();
    }
}
 
[TestFixture]
public class QuxTest : TestBase
{
    public override void SetUp()
    {
        base.SetUp();
 
        ServiceLocator.Current.GetInstance<IUnityContainer>().RegisterType<IFoo, Qux>();
    }
}

This way I can see an extra branch for each implementation in the test runner and I don’t have to copy-paste the test method body since it is automatically inherited in test classes:

Second approach – parametrized tests

The former approach has a significant disadvantage – everytime I create a new implementation, I have to create a new test subclass. I will use then a Nunit feature called “Parametrized tests” which allows me to inject a given set of parameters to a test method, one value after the other, and still be able to see all tests in the test runner.

[TestFixture]
public class TestBase
{
    [SetUp]
    public virtual void SetUp()
    {
        IUnityContainer   container = new UnityContainer();
        UnityServiceLocator locator = new UnityServiceLocator( container );
 
        ServiceLocator.SetLocatorProvider( () => locator );
    }
 
    public IFoo[] IFoos =
    {
        new Bar(),
        new Qux()
    };
 
    [TestCaseSource( "IFoos" )]
    public void Foo( IFoo foo )
    {
        ServiceLocator.Current.GetInstance<IUnityContainer>().RegisterInstance<IFoo>( foo );
 
        Assert.That( foo.DoFoo(), Is.GreaterThan( 0 ) );
    }
}

Note that there are few differences. First, the test class is no longer abstract. Then, the implementation of the service is injected to the test method and because of the TestCaseSource attribute, I inform NUnit where to look for values ready to be injected. When the test starts, I register the injected instance in my container so that when the service implementation asks again for the implementation bound to the service interface, it will get the same object.

Note also that this approach still allows me to select a test in the test runner:

Happy coding.

Friday, May 25, 2012

An unfortunate issue in the ADFS2 sign out page

In one of my last entries, I’ve described an inconsistent behavior of different web browsers which occurs when users click the Back button in their browsers.

This subtle issue causes a deadful issue in an environment built around the ADFS2.

The problem is as follows: you have your applications federated with the ADFS2, tons of them. Each application has the “SignOff” button which takes users to ADFS2. ADFS2 renders the signoff page (containing iframes with urls with wsignoutcleanup1.0) which uses a jscript to redirect to the login page (if the wreply parameter is included).

One of our clients alarmed us that signing off from applications still allows users to see the last visited page. Upon closer inspection, it turned out that because of the way Opera, Firefox and Chrome treat “redirect” pages (please consult the mentioned blog entry), clicking Back on the login page (after succesful sign off) takes users back to the last page they visited (the one they clicked “sign off” on). And because most pages are read from a disc cache, the page is rendered even though a user hasjust signed off from the application!

A disaster.

A general solution was proposed in the last blog entry. It involves a modified javascript where the redirect is not done in an explicit way but rather with a setTimeout which prevents the unintended browser behavior.

Luckily, when ADFS2 is deployed, web pages have both *.aspx and *.cs available for modifications. There is a chance then to change the way the script is generated in the SignOut.aspx page.

The offensive javascript, generated in the SignOut.aspx is as follows:

<script>
   window.onload = function() 
   { 
      document.location = decodeURIComponent('return_url_herex') 
   }
</script>

What you have to find out is to find a way to replace this script with the modified one.

When you open the SignOut.aspx.cs however, you will find out that there’s nothing much to do there:

//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
using System;
 
using Microsoft.IdentityServer.Web.UI;
 
/// <summary>
/// This page sends WS-Federation SignOutCleanup messages to all the relying parties
/// that the user has signed in to during this session.
/// </summary>
public partial class SignOut : SignOutPage
{
    protected void Page_Load( object sender, EventArgs e )
    {
        CleanupUrisRepeater.DataSource = SignOutMessages;
        CleanupUrisRepeater.DataBind();
    }
}

There does not seem to be an explicit way to change the way the unfortunate javascript is generated. The base class, SignOutPage, is one of the ADFS2 core classes, it comes from Microsoft.IdentityServer.Web.UI library. If you decompile the class, it seems that it registers the startup script in the constructor.

The critical detail to change is then to replace the javascript with a new one which would use the setTimeout. Since you cannot modify the SignOutPage class and its constructor (would have to decompile, modify, recompile and resign the DLL!), my approach is as follows – I introduce a new class, CustomSignOutPage, which inherits from SignOutPage. The SignOut page class will then inherit from the newly introduced one.

Since I cannot replace the base class’ constructor – my new class will always call it. That’s unfortunate as the script is registered in the constructor, using ClientScript.RegisterStartupScript. But the ClientScriptManager class lacks a way to unregister scripts! Another unfortunate issue!

What I can do however, is to generate a new window.onload handler which will replace the one created by the script registered with an existing, base class code. The intention is to have the following structure of scripts in the SignOut page:

<!-- this one cannot be replaced because of the design! unfortunate! !-->
<script>
   1:  
   2:    window.onload = function() 
   3:    { 
   4:       document.location = decodeURIComponent('return_url_here') 
   5:    }
</script>
<!-- these must be injected !-->
<script>
   1:  
   2:    window.onload = function() 
   3:    { 
   4:       setTimeout( "location.href = decodeURIComponent('return_url_here')", 10 ) 
   5:    }
</script>
<script>
   1:  
   2:    window.onunload = function(){};
   3:    window.history.navigationMode = "compatible";
   4:    window.history.go(1);
</script>

My proposal is then to change the SignOut.aspx.cs to:

//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
using System;
 
using Microsoft.IdentityServer.Web.UI;
 
/// <summary>
/// This page sends WS-Federation SignOutCleanup messages to all the relying parties
/// that the user has signed in to during this session.
/// </summary>
public partial class SignOut : CustomSignOutPage
{
    protected void Page_Load( object sender, EventArgs e )
    {
        CleanupUrisRepeater.DataSource = SignOutMessages;
        CleanupUrisRepeater.DataBind();
    }
}
 
public class CustomSignOutPage : SignOutPage
{
    protected override void OnPreRender( EventArgs e )
    {
        base.OnPreRender( e );
 
        if ( this.ClientScript.IsStartupScriptRegistered( this.GetType(), "SignoutPage" ) )
        {
            Type FederationPassiveAuth = 
                 typeof( SignOutPage )
                      .Assembly
                      .GetType( "Microsoft.IdentityServer.Web.FederationPassiveAuthentication" );
            string signOutReplyUrlFromRequest = 
                 (string)FederationPassiveAuth
                      .GetMethod( "GetSignOutReplyUrlFromRequest" )
                      .Invoke( FederationPassiveAuth, null ); 
            
             this.ClientScript.RegisterStartupScript( 
                typeof( SignOutPage ), 
               "SignoutPageFix", 
                string.Format( 
                    System.Globalization.CultureInfo.InvariantCulture, 
  "<script>window.onload = function() {{ " +
  "setTimeout( \"location.href = decodeURIComponent('{0}')\", 10 ) }}</script>", 
                        new object[]
                    {
                        JSEncode(signOutReplyUrlFromRequest)
                    }));
        }
 
        this.ClientScript.RegisterStartupScript( 
            this.GetType(), 
            "RedirectBackFix", 
              "<script>window.onunload = function(){};" +
              "window.history.navigationMode = \"compatible\";" +
              "window.history.go(1);</script>" );
 
    }
 
    public static string JSEncode(string originalValue)
    {
      if (string.IsNullOrEmpty(originalValue))
      {
          return string.Empty;
      }
      return System.Web.HttpUtility
            .UrlEncode(originalValue)
            .Replace("+", "%20").Replace("'", "%27");
    }
}

There are few things to explain.

First as you can see, I introduce a new class between the core SignOutPage class and the actual SignOut page class. The introduced class creates two scripts and injects them to the generated web page. The first script is rendered using a reflection on the FederationPassiveAuthentication type – this is because the type is internal in the ADFS2 core libraries. The second script uses the JSEncode method which is rewritten to mimic the way ADFS2 implements it.

And this is it. This does the trick and solves the unfortunate issue. Newly introduced scripts prevents the application page to be seen when users press the Back button in their browsers.

Once again – for this to work, you have to copy the above SignOut.aspx.cs and replace the original one. Remember to save a backup copy of the original file. Also, altough the solution was tested in a development environment, I can’t be 100% sure that there are no unexpected side effects of this change.