Python client for web services using WS-Security
Posted by Arsenalist on January 27th, 2007
Hopefully this entry serves as some decent documentation on how to write a Python client that accesses a web service which uses WS-Security. When I was trying to figure it out, Otu Ekanem’s response on the mailing list was invaluable. The example is relevant for any web service framework independent of programming language. This is tested with XFire 1.2.4 but can be used with .NET or other Java web service frameworks like Axis2.
When accessing a web service which has WS-Security enabled you must send very specific headers as part of your SOAP envelope in order for the request to be processed. You can read all about the glorious specification in PDF Format if you like. I’m using the Zolera Soap Infrastructure (ZSI) Library for Python which supports client stub generation. Given the generated stubs, there are two ways of adding custom headers to outgoing SOAP messages.
Method 1 – Not desirable but worth a mention
The first method involves modifying the generated code which is highly undesirable. Using the very simple SportsService web service example, you must modify the generated SportsService_client.py and edit the following line:
[sourcecode language='python']
self.binding.Send(None, None,
request, soapaction=”", **kw)
[/sourcecode]
to read
[sourcecode language='python']
self.binding.Send(None, None,
request, soapaction=”", soapheaders=(obj1,obj2) )
[/sourcecode]
where obj1 and obj2 are instances of Python objects which are serialized as part of the SOAP header. I found this way to be tedious as you have to design your classes to match the SOAP header and write additional serialization code. It is also hard to create the exact header as namespaces and prefixes tend to be a problem.
Method 2 – Probably the way to go, way more customizable
We can use DOM-like methods to modify the SOAP header and send out exactly what we need. The example implements the UsernameToken strategy but other ones can also be implemented by modifying the headers in a similar manner. The generated Port class’ binding attribute has a sig_handler attribute which can be assigned an instance of a custom class. In this custom class, we must implement two methods, sign and verify, that can modify the header and check it’s validity, respectively. The sign method takes in as argument a SoapWriter which enables us to modify the header. So without further ado, here’s the class that adds WS-Security headers to the outgoing SOAP envelope as discussed above. The code has been formatted and modified to fit the page.
[sourcecode language='python']
# Deprecated in 2.5, use the hashlib module instead:
# http://docs.python.org/lib/module-hashlib.html
import sha
import binascii
import base64
import time
import random
class SignatureHandler:
OASIS_PREFIX =
“http://docs.oasis-open.org/wss/2004/01/” +
“oasis-200401″
SEC_NS = OASIS_PREFIX +
“-wss-wssecurity-secext-1.0.xsd”
UTIL_NS = OASIS_PREFIX +
“-wss-wssecurity-utility-1.0.xsd”
PASSWORD_DIGEST_TYPE = OASIS_PREFIX +
“-wss-username-token-profile-1.0#PasswordDigest”
PASSWORD_PLAIN_TYPE = OASIS_PREFIX +
“-wss-username-token-profile-1.0#PasswordText”
def __init__(self, user, password, useDigest=False):
self._user = user
self._created = time.strftime(‘%Y-%m-%dT%H:%M:%SZ’,
time.gmtime(time.time()))
self._nonce = sha.new(str(random.random())).
digest()
if (useDigest):
self._passwordType = self.PASSWORD_DIGEST_TYPE
digest = sha.new(self._nonce + self._created +
password).digest()
# binascii.b2a_base64 adds a newline at the end
self._password = binascii.b2a_base64(digest)[:-1]
else:
self._passwordType = self.PASSWORD_PLAIN_TYPE
self._password = password
def sign(self,soapWriter):
# create element
securityElem = soapWriter._header.
createAppendElement(“”, “wsse:Security”)
securityElem.node.
setAttribute(“xmlns:wsse”, self.SEC_NS)
securityElem.node.
setAttribute(“SOAP-ENV:mustunderstand”, “1″)
# create element
usernameTokenElem = securityElem.
createAppendElement(“”, “wsse:UsernameToken”)
usernameTokenElem.node.
setAttribute(“xmlns:wsse”, self.SEC_NS)
usernameTokenElem.node.
setAttribute(“xmlns:wsu”, self.UTIL_NS)
# create element
usernameElem = usernameTokenElem.
createAppendElement(“”, “wsse:Username”)
usernameElem.node.
setAttribute(“xmlns:wsse”, self.SEC_NS)
# create element
passwordElem = usernameTokenElem.
createAppendElement(“”, “wsse:Password”)
passwordElem.node.
setAttribute(“xmlns:wsse”, self.SEC_NS)
passwordElem.node.
setAttribute(“Type”, self._passwordType)
# create element
nonceElem = usernameTokenElem.
createAppendElement(“”, “wsse:Nonce”)
nonceElem.node.
setAttribute(“xmlns:wsse”, self.SEC_NS)
# create element
createdElem = usernameTokenElem.
createAppendElement(“”, “wsse:Created”)
createdElem.node.
setAttribute(“xmlns:wsse”, self.UTIL_NS)
# put values in elements
usernameElem.
createAppendTextNode(self._user)
passwordElem.
createAppendTextNode(self._password)
# binascii.b2a_base64 adds a newline at the end
nonceElem.
createAppendTextNode(
binascii.b2a_base64(self._nonce)[:-1])
createdElem.createAppendTextNode(self._created)
def verify(self,soapWriter):
self
[/sourcecode]
Example usage of this is:
[sourcecode language='python']
from SportsService_client import *
from SportsService_types import *
locator = SportsServiceLocator()
port = locator.getSportsServiceHttpPort()
sigHandler = SignatureHandler(“user”, “password”, True)
port.binding.sig_handler = sigHandler
request = getMascotRequest()
teamObj = ns0.Team_Def(“Team”)
teamObj._name = “toronto”
request._team = teamObj
response = port.getMascot(request)
print response._out._name
[/sourcecode]
As you can see the SignatureHandler class is implementing an “interface” which enables it to process outgoing SOAP Requests. The verify method is empty but can contain code to check whether the SOAP header is valid.
If you would like write a PHP client that accesses a WS-Security enabled service, you should read Kim Cameron’s IdentityBlog entry which has links to the source code needed. If you simply want to use a PHP client for a non WS-Security web service, an earlier blog entry covers that.

June 8th, 2007 at 5:15 am
narkissos
I am not entirely certain how it came about but a few minutes after hitting my desk this morning, I found myself on Google searching for “Otu Ekanem”. Not content with browsing through the first few results page, I hit the last page and fou…
June 21st, 2007 at 4:36 pm
people are stranger
August 19th, 2007 at 4:50 pm
Excellent post !, please try to contact ZSI and included on upcoming releases !
very helpful, Thanks Dude
August 22nd, 2007 at 3:41 am
Need a favor , how to generate this header according to the 2nd method
username here
password here
much appreciated !
August 22nd, 2007 at 3:42 am
sorry,
here is the code one once more
Your merchant ID here
Your password here
August 22nd, 2007 at 3:43 am
the markup ain’t showing !!, could i email it for you ?
February 15th, 2008 at 10:06 am
Thanks for sharing.
April 30th, 2008 at 7:24 pm
Thank you very much. This has helped tremendously.
February 3rd, 2009 at 7:36 pm
You are a life saver. I’ve spent way too much time trying to talk to a SOAP server with Python already. I was ready to throw in the towel and just store the XML sample they provide as a template. The last piece of the puzzle was getting WSSE working with ZSI, and this worked flawlessly.
You should send this class upstream to ZSI.