So, if you're ever needing to do an integration of SAML into a WSDL generated SOAP proxy call in .NET then listen up. You can do this integration by deriving from the SoapExtension class and then defining an attribute to be placed on the SOAP proxy call ......
public class MySoapExtension : SoapExtension
{
public bool outgoing = true;
public bool incoming = false;
private Stream outputStream;
public Stream oldStream;
public Stream newStream;
public override Stream ChainStream(Stream stream)
{
this.outputStream = stream;
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}
public string getXMLFromCache()
{
newStream.Position = 0; // start at the beginning!
string soapString = ExtractFromStream(newStream);
return soapString;
}
private String ExtractFromStream(Stream target)
{
if (target != null)
return (new StreamReader(target)).ReadToEnd();
return "";
}
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
}
public override object GetInitializer(Type serviceType)
{
return null;
}
public override void Initialize(object initializer)
{
return;
}
public override void ProcessMessage(SoapMessage message)
{
// if we want to add a header we can hijack the output stream
// and load the xml into a document and manipulate it as is
// here we want the AfterSerialize stage
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
break;
case SoapMessageStage.AfterSerialize:
{
// assumed saml operations class
SAMLAssertion assertion = new SAMLAssertion();
// create assertion
assertion.CreateAssertion();
// generate signature
assertion.GenerateAssertionSignature();
Soap11 soap = new Soap11();
String soapBodyString = getXMLFromCache();
XmlDocument doc = new XmlDocument();
doc.LoadXml(soapBodyString);
XmlElement soapHeaderElement = soap.CreateHeaderElement(doc);
XmlElement wsseSecurityElement = doc.CreateElement("wsse", "Security", "http://schemas.xmlsoap.org/ws/2003/06/secext");
wsseSecurityElement.AppendChild(doc.ImportNode(assertion.Xml,true));
soapHeaderElement.AppendChild(wsseSecurityElement);
doc.DocumentElement.PrependChild(soapHeaderElement);
Stream appOutputStream = new MemoryStream();
StreamWriter soapMessageWriter = new StreamWriter(appOutputStream);
soapMessageWriter.Write(doc.InnerXml);
soapMessageWriter.Flush();
appOutputStream.Flush();
appOutputStream.Position = 0;
StreamReader reader = new StreamReader(appOutputStream);
StreamWriter writer = new StreamWriter(this.outputStream);
writer.Write(reader.ReadToEnd());
writer.Flush();
appOutputStream.Close();
this.outgoing = false;
this.incoming = true;
break;
}
case SoapMessageStage.BeforeDeserialize:
break;
case SoapMessageStage.AfterDeserialize:
break;
default:
throw new Exception("invalid stage!");
}
}
}
[AttributeUsage(AttributeTargets.Method)]
public class MySoapExtensionAttribute :
SoapExtensionAttribute
{
private int priority;
public override Type ExtensionType
{
get
{
// return type of extension to load
return typeof(MySoapExtension);
}
}
public override int Priority
{
get { return priority; }
set { priority = value; }
}
}
// And finally the wsdl generated proxy call
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://www.somecompany.com/SomeAPI/SomeMethod",
Use=System.Web.Services.Description.SoapBindingUse.Literal,
ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Bare)]
[return: System.Xml.Serialization.XmlElementAttribute("SomeMethod_Response",
Namespace="http://www.somecomapny.com/SomeAPI")]
[MySoapExtensionAttribute]
public SomeMethod_Response SomeMethod([System.Xml.Serialization.XmlElementAttribute(Namespace="http://www.somecompany.com/SomeAPI")] SomeMethod_Request SomeMethod_Request) {
object[] results = this.Invoke("SomeMethod", new object[] {
SomeMethod_Request});
return ((SomeMethod_Response)(results[0]));
}
So, if anyone finds themselves faced with a similar issue in .NET realize that you can hijack the output stream, process the xml as you see fit (and even validate yet again against another xsd) and retransmit the request over the wire... Just remember, what comes back has to be able to be proxied into the response object so Soap:Header information may be lost if not exposed appropriately on the server side to be consumed by the client.
Cheers.
dan
No comments:
Post a Comment