Zulfiqar's weblog

Middleware, security & random .Net

Archive for August, 2008

Sharing STS object between Active & Passive clients

Posted by zamd on August 16, 2008

Geneva Framework (aka Zermatt) has clean separation between token issuance object model (SecurityTokenService, RequestSecurityTokenRequest etc) and the protocol used to request token. By exploiting this separation, I will show you how to share a single SecurityTokenService object between active (WS-Trust) and passive (WS-Federation) clients. I have already written about Geneva and active clients here so in this post I will focus on passive clients. 

Geneva Framework comes with a sample which implements WS-Federation protocol handler in an ASPX page and for most of the scenarios you will use the same approach. Please refer to “Federation Scenario For Passive Clients” sample for details. The approach I am highlighting here is specific to those scenarios where you want to share the STS object between active & passive clients (Or you don’t want to take a dependency on asp.net pipeline). To achieve this I will create a WCF based protocol handler (using the WCF http support) for WS-Federation protocol. In this handler I will convert the WS-Federation token issuance request into Zermatt token issuance object model (which is very much similar to WS-Trust protocol) and will then forward the request to a shared STS object. 

The service contract for my WS-Federation protocol handler looks like following: 

[ServiceContract] 

public interface IWSFederationService 

{ 

    [OperationContract] 

    [WebGet] 

    Message Issue(string wa, string wtrealm, string wreply, string wctx, string wct); 

} 

I have deliberately named the parameters of this method based on the elements of WS-Federation protocols. This enables WCF QueryStringFormatter to correctly map the values from the HTTP Get “query string” on to these parameters. 

public class ProcessService : IWSFederationService 

{ 

    public Message Issue(string wa, string wtrealm, string wreply, string wctx, string wct) 

    { 

        var col = OperationContext.Current.IncomingMessageProperties["UriTemplateMatchResults"] as UriTemplateMatch; 

        // Get the shared STS object. 

        var sts = SingletonSTS.Instance; 

         

        var fedSerializer = new WSFederationSerializer(); 

        //create a WS-Federation message for the input data. 

        SignInRequestMessage fedRequest = (SignInRequestMessage)WSFederationMessage.CreateFromUri(col.RequestUri); 

        //convert the federation request into the common WS-Trust based STS model. 

        var rst = fedSerializer.CreateRequest(fedRequest, new WSTrustSerializationContext()); 

        var rstr = sts.Issue(ClaimsPrincipal.Current,rst); 

         

        var rstrStr = fedSerializer.GetResponseAsString(rstr, new WSTrustSerializationContext()); 

        //serialize the token response back into WS-Federation message. 

        var fedResponse = new SignInResponseMessage(new Uri(wreply), rstrStr); 

        fedResponse.Write(Console.Out); 

        // Set appropriate content-type etc. 

        var httpMsg =  Message.CreateMessage(MessageVersion.None, “”,new BinaryBodyWriter(fedResponse)); 

        httpMsg.Properties.Add(WebBodyFormatMessageProperty.Name, new WebBodyFormatMessageProperty(WebContentFormat.Raw)); 

        HttpResponseMessageProperty rmp = new HttpResponseMessageProperty(); 

        rmp.Headers[HttpResponseHeader.ContentType] = “text/html”; 

        httpMsg.Properties.Add(HttpResponseMessageProperty.Name, rmp); 

  

        return httpMsg; 

    } 

} 

With that I can host this service as any other WCF service and I’m ready to issue tokens based on WS-Federation protocol. 

class Program 

{ 

    static CustomSecurityTokenService sts; 

    static void Main(string[] args) 

    { 

        string stsAddress = http://localhost:9000/STS”; 

        SingletonSTS.Init(stsAddress); 

        // For active clients, simply expose this sts over a WS-Trust endpoint. 

        WSTrustServiceHost wsTrustSTSHost = new WSTrustServiceHost(new WSTrustServiceContract(SingletonSTS.Instance), new Uri(stsAddress)); 

        wsTrustSTSHost.AddServiceEndpoint(typeof(IWSTrustFeb2005SyncContract), 

           new WSHttpBinding(), new Uri(stsAddress)); 

        // Make the Active STS ready. 

        wsTrustSTSHost.Open(); 

        Console.WriteLine(“STS is now ready …”); 

        foreach(var ep in wsTrustSTSHost.Description.Endpoints) 

            Console.WriteLine(ep.Address.Uri.AbsoluteUri); 

  

        //make passive STS ready. 

        var wsFedSTSHost = new WebServiceHost(typeof(ProcessService), new Uri(http://localhost/wcf/federation”)); 

        wsFedSTSHost.AddServiceEndpoint(typeof(IWSFederationService), new WebHttpBinding(), “”); 

        wsFedSTSHost.Open(); 

        Console.ReadLine(); 

  

        wsFedSTSHost.Close(); 

        wsTrustSTSHost.Close(); 

    } 

}

Posted in Zermatt | 1 Comment »

Serializing Faults using XmlSerializer

Posted by zamd on August 15, 2008

Update (25/08/2008): .net Framework 3.5 SP1 has added the support for serializing faults using XML Serializer.

[XmlSerializerFormat(SupportFaults=true)]: by setting SupportFaults=true will result in the use of Xml Serializer for fault serialization as well. The default value is false to maintain backward compatibility. The approach mentioned in this article is still valid for scenarios where you want to use DataContractSerializer for input & output messages but XML Serializer for faults (a rare requirement though).

——————————————————————————————————————————————

 

Today someone asked me how to serialize the TDetail part of FaultException<TDetail> using XmlSerializer. The scenario was to interoperate with an existing schema using some of the XSD features (attributes etc.), which are currently not supported by DataContractSerializer. The solution is to Subclass the MessageFault class and use XmlSerializer to serialize the TDetail.

public class XmlSerializerMessageFault : MessageFault

{

    FaultCode code;

    FaultReason reason;

    object details;

    public XmlSerializerMessageFault(FaultCode code, FaultReason reason, object details)

    {

        this.details = details;

        this.code = code;

        this.reason = reason;

    }

    public override FaultCode Code

    {

        get { return code; }

    }

    public override bool HasDetail

    {

        get { return (details != null); }

    } 

    protected override void OnWriteDetailContents(System.Xml.XmlDictionaryWriter writer)

    {

        var ser = new XmlSerializer(details.GetType());

        ser.Serialize(writer, details);

        writer.Flush();

    } 

    public override FaultReason Reason

    {

        get { return reason; }

    }

}

Fault was created from a global error handler (IErrorHandler).

public class ErrorHandlerEx : IErrorHandler

{

    public bool HandleError(Exception error)

    {

        return true;

    } 

    public void ProvideFault(

        Exception error, MessageVersion version, ref Message fault)

    {

        if (error is FaultException)

        {

            // extract our FaultContract object from the exception object.

            var detail = error.GetType().GetProperty(“Detail”).GetGetMethod().Invoke(error, null);

            // create a fault message containing our FaultContract object

            var msgFault = new XmlSerializerMessageFault(FaultCode.CreateSenderFaultCode(“systemDown”, http://zamd.net&#8221;), new FaultReason(“System is down”), detail);

            fault = Message.CreateMessage(version, msgFault, http://zamd.net/action&#8221;);

        }

    }

}

Xml output

<Fault xmlns=http://schemas.microsoft.com/ws/2005/05/envelope/none>

  <Code>

    <Value>Sender</Value>

    <Subcode>

      <Value xmlns:a=http://zamd.net>a:systemDown</Value>

    </Subcode>

  </Code>

  <Reason>

    <Text xml:lang=en-GB>System is down</Text>

  </Reason>

  <Detail>

    <GreaterThan3Fault ErrorCode=90192 Location=ISB xmlns=“” xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance xmlns:xsd=http://www.w3.org/2001/XMLSchema>

      <FaultMessage>Count cannot be greate than 3. Please try again later.</FaultMessage>

    </GreaterThan3Fault>

  </Detail>

</Fault>

Posted in WCF | 2 Comments »

Calculating WCF Message Size

Posted by zamd on August 15, 2008

Here I have talked about various encoding options and their impact on the message size (byte stream produced by WCF). The question is how you can measure the size of the byte stream produced by WCF?

Size of the byte stream depends on the encoder being used and different encoder produced byte stream of different sizes.

I have written a MessageInspector, which does message/stream rewriting to calculate the size of the final byte stream. Once you have configured this inspector in your client it will print the difference in the size of message created by text encoder vs your configured encoder (MTOM, Binary or GZip).  Send method of Inspector looks like following:

public object BeforeSendRequest(ref System.ServiceModel.Channels.Message request, IClientChannel channel)

{

    var mb = request.CreateBufferedCopy(int.MaxValue);

    request = mb.CreateMessage();

    var ms = new MemoryStream();

 

    // Dump message size based on text encoder.

    using (var memWriter = XmlDictionaryWriter.CreateTextWriter(ms))

    {

        mb.CreateMessage().WriteMessage(memWriter);

        memWriter.Flush();

        Console.WriteLine("Message size using text encoder {0}", ms.Position);

    }

    ms = new MemoryStream();

    if (gzipEncoding != null)

    {// GZip Special case

        var encoder = gzipEncoding.CreateMessageEncoderFactory().CreateSessionEncoder();

        encoder.WriteMessage(mb.CreateMessage(), ms);

        Console.WriteLine("GZip encoded size {0}", ms.Position);

        return null;

    }

    // just wrap the message – and wrapper will do the trick.

    request = new WrappingMessage(request);

    return null;

}

 

public class WrappingMessage : Message

{

    Message innerMsg;

    MessageBuffer msgBuffer;

    public WrappingMessage(Message inner)

    {

        this.innerMsg = inner;

        msgBuffer = innerMsg.CreateBufferedCopy(int.MaxValue);

        innerMsg = msgBuffer.CreateMessage();

    }

    public override MessageHeaders Headers

    {

        get { return innerMsg.Headers; }

    }

 

    protected override void OnWriteBodyContents(XmlDictionaryWriter writer)

    {

        innerMsg.WriteBodyContents(writer);

    }

 

    public override MessageProperties Properties

    {

        get { return innerMsg.Properties; }

    }

 

    public override MessageVersion Version

    {

        get { return innerMsg.Version; }

    }

 

    protected override void OnWriteMessage(XmlDictionaryWriter writer)

    {

        // write message to the actual stream using encoder..

        base.OnWriteMessage(writer);

        writer.Flush();

        // write message to MemoryStream (using encoder) to get it’s size.

        var copy = msgBuffer.CreateMessage();

        DumpEncoderSize(writer, copy);

    }

    private static void DumpEncoderSize(System.Xml.XmlDictionaryWriter writer, Message copy)

    {

        var ms = new MemoryStream();

        string configuredEncoder = string.Empty;

        if (writer is IXmlTextWriterInitializer)

        {

            var w = (IXmlTextWriterInitializer)writer;

            w.SetOutput(ms, Encoding.UTF8, true);

            configuredEncoder = "Text";

        }

        else if (writer is IXmlMtomWriterInitializer)

        {

            var w = (IXmlMtomWriterInitializer)writer;

            w.SetOutput(ms, Encoding.UTF8, int.MaxValue, "", null, null, true, false);

            configuredEncoder = "MTOM";

        }

        else if (writer is IXmlBinaryWriterInitializer)

        {

            var w = (IXmlBinaryWriterInitializer)writer;

            w.SetOutput(ms, null, null, false);

            configuredEncoder = "Binary";

        }

        copy.WriteMessage(writer);

        writer.Flush();

        var size = ms.Position;

        Console.WriteLine("Message size using configured ({1}) encoder {0}",  size,configuredEncoder);

    }

 

}

I have attached complete solution with this post.

Download: MessageSize.zip

Posted in WCF | 4 Comments »

 
Follow

Get every new post delivered to your Inbox.