Showing posts with label wcf. Show all posts
Showing posts with label wcf. Show all posts

Wednesday, 7 May 2008

Enabling tracing for WCF (then making sense of the output)

I've been battling with a few of WCF's "endearing" foibles over the last couple of days (like this one). One thing that I really should have noticed earlier to help with this battle is that you can enable tracing for WCF. MSDN gives this example:

<configuration>
   <system.diagnostics>
      <sources>
            <source name="System.ServiceModel" 
                    switchValue="Information, ActivityTracing"
                    propagateActivity="true">
            <listeners>
               <add name="traceListener" 
                   type="System.Diagnostics.XmlWriterTraceListener" 
                   initializeData= "c:\log\Traces.svclog" />
            </listeners>
         </source>
      </sources>
   </system.diagnostics>
</configuration>

This produces a fairly unreadable mess, but you can use the Service Tracer Viewer (SvcTraceViewer.exe) that comes with the Windows SDK (C:\Program Files\Microsoft SDKs\Windows\v6.0A\Bin on my PC) to get some useful information out of it. This is actually quite a cool tool, providing a nice interface to view all the different activities happening behind the scenes, and highlighting errors that occur.

WCF Trace Viewer screen shot

Friday, 2 May 2008

Adding IPrincipal to the list of known types in WCF

WCF has a concept called Known Types, which lets WCF handle subclasses and interface implementations passed to services. Normally you can just whack a KnownTypeAttribute on your DataContract and you're good to go:

[DataContract]
[KnownType(typeof(Manager))]
public class Employee { ... }

In the example above, the service will be able to use the Manager subclass anywhere it expects an Employee.

This doesn't help much when you want to use non-DataContract types like IPrincipal. Trying to pass a GenericPrincipal to a service operation that expects an IPrincipal gives you an error message like this (line breaks added for slightly improved legibility):

System.Runtime.Serialization.SerializationException:
Type 'System.Security.Principal.GenericPrincipal' with data contract name 'GenericPrincipal:http://schemas.datacontract.org/2004/07/System.Security.Principal' is not expected. 
Add any types not known statically to the list of known types - 
for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.

One solution is to decorate your service contract with a ServiceKnownTypeAttribute:

[ServiceContract]
[ServiceKnownType(typeof(GenericIdentity))]
[ServiceKnownType(typeof(GenericPrincipal))]
public interface ISomeService {
  [OperationContract]
  String DoSomethingWith(IPrincipal principal);
}

While I don't necessarily recommend exposing IPrincipal through your service, the technique is worth knowing about. Most of the search results I got mentioned KnownTypeAttribute and not ServiceKnownTypeAttribute.

Thanks to Steven Cheng who answered this question in his response to this post. Steven also provided a link to Bennie Haelen's fantastic post on Handling Data Contract Object Hierarchies in WCF, which covers more than you could ever want to know about this stuff :-).

Monday, 21 April 2008

Faking WCF authentication for integration tests

I am currently trying to write some integration tests for a WCF service. The service itself calls a controller that uses ServiceSecurityContext.Current.PrimaryIdentity to get the current user, then checks what the user can do. For the purpose of my integration tests, I wanted to hard code the current user, and then expect certain results back for that user.

Now the username lookup is nicely abstracted behind an identity provider interface, so I would ideally just use a dependency injection tool to replace the identity lookup with something that suits my tests. For a number of reasons this wasn't possible, so instead I had to override the WCF authentication process to always return the required user.

First thing was to setup a ServiceHost so the test runner could self-host the WCF service. You can test the service directly without hosting, but in this case we wanted to test things through a host. This implementation isn't careful about disposing of resources properly, so I'd think twice about copying any of this for real use if I were you :-).

[TestFixture]
public class MyServiceIntegrationTests {

  private ServiceHost host;
  private ServiceEndpoint serviceEndpoint;

  [TestFixtureSetUp]
  public void FixtureSetup() {
    host = new ServiceHost(typeof(MyService), new Uri("http://localhost:8080/TestMyService"));

    //Hack up host security for testing
    host.Authorization.ExternalAuthorizationPolicies
        = new List<IAuthorizationPolicy> { new TestAuthPolicy() }.AsReadOnly();
    host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.Custom;

    //Add endpoint
    serviceEndpoint = host.AddServiceEndpoint(typeof (IMyService), new BasicHttpBinding(), String.Empty);
    
    host.Open();
  }
  [TestFixtureTearDown]
  public void CleanupAfterAllTests() {
    host.Close();
  }
  ...
}  

The emphasised bit is where we rip into the WCF authorisation process, by setting the permission mode to PrincipalPermissionMode.Custom, and substituting our own IAuthorizationPolicy instance, TestAuthPolicy, which looks like this:

internal class TestAuthPolicy : IAuthorizationPolicy {
  public string Id { get { return "TestAuthPolicy"; } }

  public bool Evaluate(EvaluationContext evaluationContext, ref object state) {  
    evaluationContext.Properties["Principal"] = new TestPrincipal();
    IList<IIdentity> identities = new List<IIdentity> {new TestIdentity()};
    evaluationContext.Properties.Add("Identities", identities);
    return true;
  }

  public ClaimSet Issuer { get { return ClaimSet.System; } }
}

Here we implement the Evaluate method to update the EvaluationContext properties. We set the Principal property to our own TestPrincipal object, and to set the Identities property to use our own TestIdentity. These test objects are rigged to always return our required test principal and identity:

internal class TestPrincipal : IPrincipal {
  public bool IsInRole(string role) { return true; }
  public IIdentity Identity { get { return new TestIdentity(); } }
}

internal class TestIdentity : IIdentity {
  public string Name { get { return @"MyTestDomain\MyTestUser"; } }
  public string AuthenticationType {
    get { return "Dodgy auth"; }
  }
  public bool IsAuthenticated { get { return true; } }
}

The integration test can then be run like this:

[Test]
[Category("Integration")]
public void Should_be_able_to_call_my_poxy_WCF_service() {
  using (var channelFactory = new ChannelFactory<IMyService>(serviceEndpoint)) {  
    var service = channelFactory.CreateChannel();
    var output = service.DoStuff("Please work!");
    Assert.That(output, Is.EqualTo("Worked! Hurrah!"));
    ((IChannel)service).Close();
  }
}

So here's what we've done. We've self-hosted a WCF service in the test fixture, and overridden the authorisation process used by that host to make it look like we always have an authenticated "MyTestDomain\MyTestUser" user. We then created a ChannelFactory to connect to that host and test the service from end-to-end (well, except for the authorisation part things ;-)). You can obviously muck around with the custom authorisation bits to change the user on the fly, so you can test different users and situations where the user is not authenticated.

Hope this helps. Let me know if I've missed easier ways of doing this :-)

Thursday, 17 April 2008

Getting the current identity in WCF

Playing around with an ASP.NET-hosted WCF service today. While testing using the WCF Test Client I (unsurprisingly in retrospect) got a null reference exception when trying to access HttpContext.Current. This completely stuffed up my attempt to get the current identity from HttpContext.Current.User.Identity.

After a quick search I found the ServiceSecurityContext class in the System.ServiceModel namespace. This gives you the current identity using ServiceSecurityContext.Current.PrimaryIdentity (and also exposes the current WindowsIdentity).

Helpfully enough my code was well factored for once (to help with my unit testing/TDDing) and I only had to make the change in one place :)

Might be some more gotchas around this depending on what authentication mechanism you are using, but this was enough to get things working for me.