Arsenalist

The Toronto Raptors Blog with an Arsenal touch

Python client for web services using WS-Security

Posted by Arsenalist on January 27, 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:

self.binding.Send(None, None,
   request, soapaction="", **kw)

to read

self.binding.Send(None, None,
   request, soapaction="", soapheaders=(obj1,obj2) )

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.

# 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

Example usage of this is:

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

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.

Advertisements

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

  1. 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 said

    people are stranger

  3. Amrlafi said

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

  4. Amrlafi said

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

    username here
    password here

    much appreciated !

  5. Amrlafi said

    sorry,
    here is the code one once more

    Your merchant ID here
    Your password here

  6. Amrlafi said

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

  7. Torito said

    Thanks for sharing.

  8. Joe said

    Thank you very much. This has helped tremendously.

  9. PhilGo20 said

    Hi,

    Having some similar problems with Python Soap and WSSE. Could you share you actual client implementation ?

    from SportsService_client import *
    02
    from SportsService_types import *

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: