2

I am trying to write unit tests to cover my ExternalAuthenticationService class; I'm trying to test my RedirectToLogin method and ensure that the RelayStateQuery is populated like it should be:

public IActionResult RedirectToLogin(LoginInfo loginInfo)
{
    var binding = new Saml2RedirectBinding();

    var dict = new Dictionary<string, string>
    {
        { "SomeKey", info.SomeStringIWantToRoundTrip }
    };

    binding.SetRelayStateQuery(dict);
            
    return binding.Bind(new Saml2AuthnRequest(config)).ToActionResult();
}

So I've tried to setup my test like this:

public class ExternalAuthenticationServiceTests
{
    private IExternalAuthenticationService service;
    public ExternalAuthenticationServiceTests()
    {
        var testCertificate = GenerateCertificate("Test Certificate");
        var config = new Saml2Configuration
        {
            CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None,
            Issuer = "http://wonderwoman.com",
            RevocationMode = X509RevocationMode.NoCheck,
            SignatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
            SigningCertificate = testCertificate
        };
        service = new ExternalAuthenticationService(config);
    }


    [Fact]
    public void RedirectToLogin_sets_tenantName_in_RelayStateQuery()
    {
        LoginInfo loginInfo = new() { 
           SomeStringIWantToRoundTrip = "user location or whatever" 
        };
        var result = service.RedirectToLogin(loginInfo);
    }

    private X509Certificate2 GenerateCertificate(string subjectName)
    {
        // Generate a new RSA key pair
        using var rsa = RSA.Create(2048);
        // Create a certificate request
        CertificateRequest request = new($"CN={subjectName}", rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

        // Set certificate validity dates
        DateTimeOffset now = DateTimeOffset.UtcNow;
        request.CertificateExtensions.Add(new X509BasicConstraintsExtension(false, false, 0, false));
        request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature | X509KeyUsageFlags.KeyEncipherment, false));
        request.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));
        request.CertificateExtensions.Add(new X509SubjectKeyIdentifierExtension(request.PublicKey, false));

        // Create a self-signed certificate
        X509Certificate2 certificate = request.CreateSelfSigned(now, now.AddYears(1));

        // Return the generated certificate
        return certificate;
    }
  }
}

…but this doesn't work, even before assertions are added. The RedirectToLogin method throws an exception:

Message:  System.NullReferenceException : Object reference not set to an instance of an object.

Stack Trace: 

Saml2RedirectBinding.BindInternal(Saml2Request saml2RequestResponse, String messageName)
Saml2Binding`1.Bind(Saml2Request saml2Request)
ExternalAuthenticationService.RedirectToLogin(LoginInfo loginInfo) line 39

I'm assuming the Saml2Configuration class needs something I'm not providing in the properties, but I can't tell what. What am I missing?

Scott Baker
  • 10,013
  • 17
  • 56
  • 102

1 Answers1

1

I was missing a SingleSignOnDestination:

var config = new Saml2Configuration
{
    CertificateValidationMode = System.ServiceModel.Security.X509CertificateValidationMode.None,
    Issuer = "http://wonderwoman.com",
    RevocationMode = X509RevocationMode.NoCheck,
    SignatureAlgorithm = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
    SigningCertificate = testCertificate,
    SingleSignOnDestination = new Uri("http://test.com"), // required - duh
};
Scott Baker
  • 10,013
  • 17
  • 56
  • 102