Arsenalist

Arsenal blog by a Canadian

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.

9 Responses to “Python client for web services using WS-Security”

  1. il maestro ignoto Says:

    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…

  2. emurhfkq Says:

    people are stranger

  3. Amrlafi Says:

    Excellent post !, please try to contact ZSI and included on upcoming releases !
    very helpful, Thanks Dude

  4. Amrlafi Says:

    Need a favor , how to generate this header according to the 2nd method

    username here
    password here

    much appreciated !

  5. Amrlafi Says:

    sorry,
    here is the code one once more

    Your merchant ID here
    Your password here

  6. Amrlafi Says:

    the markup ain’t showing !!, could i email it for you ?

  7. Torito Says:

    Thanks for sharing.

  8. Joe Says:

    Thank you very much. This has helped tremendously.

  9. cj_ Says:

    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.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>