Showing posts with label WCF. Show all posts
Showing posts with label WCF. Show all posts

Monday, 18 August 2008

Custom WCF Channel in Silverlight

Here's some code I use to create a custom channel in Silverlight. I use this to mimic the IMessageInspector behaviour available in the standard WCF stack (but alas, not in Silverlight).

Firstly you'll need a Custom BindingElement:

/// <summary>
/// Custom binding element - builds the inner channel
/// </summary>
public class ClientChannelBindingElement : BindingElement
{
#region Constructors
/// <summary>
/// Default constructor
/// </summary>
public ClientChannelBindingElement()
{
}
/// <summary>
/// Protected constructor
/// </summary>
/// <param name="other">Another binding element</param>
protected ClientChannelBindingElement(ClientChannelBindingElement other)
: base(other)
{
}
#endregion
#region Public methods
/// <summary>
/// Build a channel factory
/// </summary>
/// <typeparam name="TChannel">Channel of interest</typeparam>
/// <param name="context">Binding context</param>
/// <returns>A new channel factory</returns>
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
ClientChannelFactory<TChannel> factory = new ClientChannelFactory<TChannel>();
factory.InnerChannelFactory = context.BuildInnerChannelFactory<TChannel>();
return factory;
}
/// <summary>
/// Copy this object
/// </summary>
/// <returns>A copy of this</returns>
public override BindingElement Clone()
{
return new ClientChannelBindingElement(this);
}
/// <summary>
/// Gets an element property
/// </summary>
/// <typeparam name="T">Channel of interest</typeparam>
/// <param name="context">Binding context</param>
/// <returns>Property value</returns>
public override T GetProperty<T>(BindingContext context)
{
return context.GetInnerProperty<T>();
}
#endregion
}


Secondly, you'll need to reference this from a binding. I've made this binding look the same as a BasicHttpBinding, so that it may be used from Silverlight:

/// <summary>
/// Custom Silverlight binding (mimics BasicHttpBinding)
/// </summary>
public class ClientChannelBinding : CustomBinding
{
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public ClientChannelBinding()
{
BasicHttpBinding basic = new BasicHttpBinding();
this.Elements.Add(new ClientChannelBindingElement());
this.Elements.Add(new TextMessageEncodingBindingElement(basic.MessageVersion, basic.TextEncoding));
this.Elements.Add(new HttpTransportBindingElement());
}
#endregion
#region Public methods
/// <summary>
/// Builds the channel factory
/// </summary>
/// <typeparam name="TChannel">Channel of interest</typeparam>
/// <param name="parameters">Binding parameters</param>
/// <returns>An encapsulated channel factory</returns>
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingParameterCollection parameters)
{
ClientChannelFactory<TChannel> factory = new ClientChannelFactory<TChannel>();
BasicHttpBinding binding = new BasicHttpBinding();
factory.InnerChannelFactory = binding.BuildChannelFactory<TChannel>();
return factory;
}
#endregion
#region Internal methods
/// <summary>
/// Async Opened event handler
/// </summary>
/// <typeparam name="TChannel">Channel of interest</typeparam>
/// <param name="result">Async result</param>
internal void OnOpened<TChannel>(IAsyncResult result)
{
if (result.CompletedSynchronously)
{
return;
}
else
{
CompleteOpen<TChannel>(result);
}
}
/// <summary>
/// Async callback handler
/// </summary>
/// <typeparam name="TChannel">Channel of interest</typeparam>
/// <param name="result">Async result</param>
internal void CompleteOpen<TChannel>(IAsyncResult result)
{
IChannelFactory<TChannel> factory = (IChannelFactory<TChannel>)result.AsyncState;
factory.EndOpen(result);
}
#endregion
}


Next, you'll need a channel ...

/// <summary>
/// Silverlight client channel class
/// </summary>
public class ClientChannel : IRequestChannel
{
#region Fields
/// <summary>
/// Internal channel
/// </summary>
private IRequestChannel innerChannel;
#endregion
#region Constructors
/// <summary>
/// Channel Constructor
/// </summary>
/// <param name="innerChannel">Inner channel</param>
public ClientChannel(IRequestChannel innerChannel)
{
this.innerChannel = innerChannel;
}
#endregion
#region Events
/// <summary>
/// Close event handler
/// </summary>
public event EventHandler Closed;
/// <summary>
/// Closing event handler
/// </summary>
public event EventHandler Closing;
/// <summary>
/// Faulted event handler
/// </summary>
public event EventHandler Faulted;
/// <summary>
/// Open event handler
/// </summary>
public event EventHandler Opened;
/// <summary>
/// Opening event handler
/// </summary>
public event EventHandler Opening;
#endregion
#region Properties
/// <summary>
/// Gets the remote address of the channel
/// </summary>
public EndpointAddress RemoteAddress
{
get
{
return this.innerChannel.RemoteAddress;
}
}
/// <summary>
/// Gets the coomunication state object
/// </summary>
public CommunicationState State
{
get
{
return this.innerChannel.State;
}
}
/// <summary>
/// Gets the URI (from inner channel)
/// </summary>
public Uri Via
{
get
{
return this.innerChannel.Via;
}
}
#endregion
#region Methods
/// <summary>
/// Async begin request
/// </summary>
/// <param name="message">Message to send</param>
/// <param name="timeout">Timeout period</param>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)
{

// In Silverlight this is the place to add the header to the outgoing request
... ADD THE HEADER HERE ...
this.Decorate(message);
IAsyncResult result = this.innerChannel.BeginRequest(message, timeout, callback, state);
return result;
}
/// <summary>
/// Async begin request
/// </summary>
/// <param name="message">Message to send</param>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)
{

... ADD THE HEADER HERE ...
this.Decorate(message);
IAsyncResult result = this.innerChannel.BeginRequest(message, callback, state);
return result;
}
/// <summary>
/// End the request - parse the reply for the security header
/// </summary>
/// <param name="result">Async results object</param>
/// <returns><c>Message</c> reply</returns>
public Message EndRequest(IAsyncResult result)
{

// In Silverlight this is the place to read the header from the incoming reply
Message reply = this.innerChannel.EndRequest(result);

... READ THE HEADER HERE ...
return reply;
}
/// <summary>
/// Synchronous request
/// </summary>
/// <param name="message">Outgoing message</param>
/// <param name="timeout">Timeout period</param>
/// <returns>Modified message</returns>
public Message Request(Message message, TimeSpan timeout)
{
this.Decorate(message);
return this.innerChannel.Request(message, timeout);
}
/// <summary>
/// Synchronous request
/// </summary>
/// <param name="message">Outgoing message</param>
/// <returns>Modified message</returns>
public Message Request(Message message)
{
this.Decorate(message);
return this.innerChannel.Request(message);
}
/// <summary>
/// Gets a channel property
/// </summary>
/// <typeparam name="T">Type of property</typeparam>
/// <returns>Property result</returns>
public T GetProperty<T>() where T : class
{
return this.innerChannel.GetProperty<T>();
}
/// <summary>
/// Abort this channel
/// </summary>
public void Abort()
{
this.innerChannel.Abort();
}
/// <summary>
/// Asynchronous close
/// </summary>
/// <param name="timeout">Timeout period</param>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginClose(timeout, callback, state);
}
/// <summary>
/// Asynchronous close
/// </summary>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginClose(AsyncCallback callback, object state)
{
return this.innerChannel.BeginClose(callback, state);
}
/// <summary>
/// Asynchronous open
/// </summary>
/// <param name="timeout">Timeout period</param>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state)
{
return this.innerChannel.BeginOpen(timeout, callback, state);
}
/// <summary>
/// Asynchronous open
/// </summary>
/// <param name="callback">Callback method</param>
/// <param name="state">State information</param>
/// <returns>Asynchronous result</returns>
public IAsyncResult BeginOpen(AsyncCallback callback, object state)
{
return this.innerChannel.BeginOpen(callback, state);
}
/// <summary>
/// Synchronous close
/// </summary>
/// <param name="timeout">Timeout period</param>
public void Close(TimeSpan timeout)
{
this.innerChannel.Close(timeout);
}
/// <summary>
/// Synchronous close
/// </summary>
public void Close()
{
this.innerChannel.Close();
}
/// <summary>
/// Asynchronous callback - close
/// </summary>
/// <param name="result">Async result</param>
public void EndClose(IAsyncResult result)
{
this.innerChannel.EndClose(result);
}
/// <summary>
/// Asynchronous callback - open
/// </summary>
/// <param name="result">Async result</param>
public void EndOpen(IAsyncResult result)
{
this.innerChannel.EndOpen(result);
}
/// <summary>
/// Synchronous open
/// </summary>
/// <param name="timeout">Timeout period</param>
public void Open(TimeSpan timeout)
{
this.innerChannel.Open(timeout);
}
/// <summary>
/// Synchronous open
/// </summary>
public void Open()
{
this.innerChannel.Open();
}
#endregion
#region Protected Methods
/// <summary>
/// Decorate the message with the new headers
/// </summary>
/// <param name="message">Message to decorate</param>
protected virtual void Decorate(Message message)
{
message.Headers.Add(new SecurityHeader());
}
#endregion
}


[Note: the SecurityHeader class is derived from the MessageHeader class]

... and an associated channel factory:


/// <summary>
/// Overriden custom channel factory
/// </summary>
/// <typeparam name="TChannel">Type of channel to create</typeparam>
public class ClientChannelFactory<TChannel> : ChannelFactoryBase<TChannel>
{
#region Fields
/// <summary>
/// Inner channel factory
/// </summary>
private IChannelFactory<TChannel> innerChannelFactory;
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public ClientChannelFactory()
{
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the inner channel factory
/// </summary>
public IChannelFactory<TChannel> InnerChannelFactory
{
get
{
return this.innerChannelFactory;
}
set
{
this.innerChannelFactory = value;
}
}
#endregion
#region Protected methods
/// <summary>
/// Open event handler
/// </summary>
/// <param name="timeout">Timeout period</param>
protected override void OnOpen(TimeSpan timeout)
{
this.innerChannelFactory.Open(timeout);
}
/// <summary>
/// Create channel evebt handler
/// </summary>
/// <param name="to">Destination of channel</param>
/// <param name="via">Via this URI</param>
/// <returns>Newly created channel</returns>
protected override TChannel OnCreateChannel(EndpointAddress to, Uri via)
{
TChannel innerchannel = this.innerChannelFactory.CreateChannel(to, via);
if (innerchannel is IRequestChannel)
{
ClientChannel clientChannel = new ClientChannel((IRequestChannel)innerchannel);
return (TChannel)(object)clientChannel;
}
return innerchannel;
}
/// <summary>
/// End open async callback handler
/// </summary>
/// <param name="result">Async result</param>
protected override void OnEndOpen(IAsyncResult result)
{
this.innerChannelFactory.EndOpen(result);
}
/// <summary>
/// Begin open async handler
/// </summary>
/// <param name="span">Timeout span</param>
/// <param name="callback">Callback method</param>
/// <param name="asyncState">Async state object</param>
/// <returns>Async result</returns>
protected override IAsyncResult OnBeginOpen(TimeSpan span, AsyncCallback callback, object asyncState)
{
return this.innerChannelFactory.BeginOpen(callback, asyncState);
}
#endregion
}


Finally, you'll need to create the channel from your code (the client class is autogenerated when you import the service):


ClientChannelBinding binding = new ClientChannelBinding();
EndpointAddress endpoint = new EndpointAddress("http://localhost:3532/Authentication.svc"); ////TODO get this from config
this.client = new Authentication.AuthenticationClient(binding, endpoint);

Happy coding!

Graeme

Friday, 28 March 2008

Federation over a Duplex channel

Me again.

This time I wanted to do something a little beyond the norm, at least as far as the available examples are concerned. I would really like to implement a publish/subscriber service, where an application subscribes to various system wide events. Seems like a job for a Duplex channel - the client subscribes to service, and also provides an endpoint as a means to receive events.

The big catch here is that I'd love this service to execute under the same security context as the rest of my architecture, i.e. I'd like the service to be federated with my STS.

After trawling the net, I found several useful snippets, but nothing that solved the whole problem. Specifically, I wanted to achieve all this via config, if possible.

So here's how I did it...


The client binding looks like

<customBinding>
<binding name="DuplexBinding">
<security authenticationMode="SecureConversation">
<secureConversationBootstrap authenticationMode="IssuedToken">
<issuedTokenParameters tokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1">
<issuer address="http://localhost:8080/STS/Slave" binding="wsFederationHttpBinding" bindingConfiguration="UserNameFederationBinding">
<identity>
<dns value="My STS" />
</identity>
</issuer>
<issuerMetadata address="http://localhost:8080/STS/Slave/mex" />
</issuedTokenParameters>
</secureConversationBootstrap>
</security>
<compositeDuplex clientBaseAddress="http://localhost:8080/Client"/>
<oneWay/>
<textMessageEncoding />
<httpTransport />
</binding>
</customBinding>

Here, the original authentication mechanism was via a Username token. Notice the order of the elements within the tag - this foxed me for a good while before it clicked! Also note that the authentication mode is set to 'SecureConversation', with the bootstrap mode set to 'IssuedToken'. Initially I thought this seemed counter-intuitive, but when you consider the order in which WCF (or more correctly, WS-Security) performs its negotiation, this makes more sense.

In the service, the binding looks like

<customBinding>
<binding name="DuplexBinding">
<security authenticationMode="SecureConversation">
<secureConversationBootstrap authenticationMode="IssuedToken">
<issuedTokenParameters tokenType="http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1">
<issuer address="http://localhost:8080/STS/Slave" binding="wsHttpBinding" />
<issuerMetadata address="http://localhost:8080/STS/Slave/mex" />
</issuedTokenParameters>
</secureConversationBootstrap>
</security>
<compositeDuplex/>
<oneWay/>
<textMessageEncoding />
<httpTransport />
</binding>
</customBinding>

The service works in exactly the same way a standard Duplex service would, i.e. it simply retains a reference to the callback channel, like so

IRegisterCallback callback = OperationContext.Current.GetCallbackChannel();

in order to use this when necessary.

I hope this helps anybody else wishing to do the same, or similar, with their architecture.

Cheers

Graeme

Thursday, 21 February 2008

SAML token serialization

So, as promised, my first post is regarding simple serialization/de-serialization of a SAML token.





The driver for this exercise was to implement the 'Renew' verb of the WS-Trust specification, using the framework that WCF provides. To my endless frustration, the development team over at Microsoft stopped short of implementing the entire WS-Trust spec for anything but SCTs! Instead we're left with just 'Issue'.





My STS is signing tokens using self-issued X509 certificates (one for the STS and one for each client). Here's the (shortened) code for the serialization of the token:




///
/// Write the given token into an XmlWriter object
///

///
///
public virtual void WriteToken(SamlSecurityToken token, XmlWriter writer)
{
System.ServiceModel.Security.WSSecurityTokenSerializer serializer = new System.ServiceModel.Security.WSSecurityTokenSerializer();
serializer.WriteToken(writer, token);
}


Now, when I receive the token back from the client for renewal I need to de-serialize it in order to update its validity time span. Here's the code for this:



///
/// Read the token from an XmlDictionaryReader reader
///

///
///
public virtual SamlSecurityToken ReadToken(XmlDictionaryReader reader)
{
System.ServiceModel.Security.WSSecurityTokenSerializer serializer = new System.ServiceModel.Security.WSSecurityTokenSerializer();
System.Collections.Generic.List tokens = new System.Collections.Generic.List();
tokens.Add(SigningToken);
tokens.Add(UnencryptingToken);
System.IdentityModel.Selectors.SecurityTokenResolver resolver =
System.IdentityModel.Selectors.SecurityTokenResolver.CreateDefaultSecurityTokenResolver(tokens.AsReadOnly(), false);
SamlAssertion assertion = new SamlAssertion();
assertion.ReadXml(reader, new SamlSerializer(), serializer, resolver);
return new SamlSecurityToken(assertion);
}



Note that here the signing token is the certificate used by the STS to sign the assertion, and the unencrypting token is the private key equivalent of the certificate used to encrypt the proof key in the RSTR.

The point here is that in order to de-serialize the token, you're going to need the private key of the certificate used to encrypt the original. I guess this isn't a problem when you're in control of client certification, but I think you can see why this might cause issues.