WS-Security and Python, a Blackboard Story
We have a current integration with Blackboard Vista and as we start to deploy Blackboard Learn we wanted to use this same integration with this new end point. The feed is written in Python and the Learn web services use WS-Security for authentication/authorization. I could find very little information on WS-Security and Python. We decided to go with ZSI for our web services layer in Python. After digging for a long time we found that ZSI allows a hook to a Signature Handler that signs each soap packet before its was sent! PERFECT… Now what!
This is my first WS-Security via Python so I am not sure if there is some liberty taken with this protocol by Blackboard or not. The Blackboard looks like it uses the Rampart module for Axis, so I would hope it is standard. Below is our Signature Handler that is working GREAT!
Also below is a sample of how its used for Blackboard Learn. Learn uses a sessionId in the “password” field for WS-Security. This session is then authenticated using web services calls. The info below connects the Context web services for the first time and requests a session. You can then reuse the sessionId it creates and just reuse the Signature Handler created for the other web services calls.
Complete Signature Handler
import time import random import hashlib import binascii import logging ''' Created on Oct 27, 2010 @author: cdg2 ''' class SignatureHandler(object): ''' classdocs ''' 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" log=logging.getLogger("SignatureHandler") def __init__(self,user,password,useDigest=False): ''' Constructor ''' self._user=user self._created=time.strftime('%Y-%m-%dT%H:%M:%SZ',time.gmtime(time.time())) self._nonce=hashlib.new("sha",str(random.random())).digest() if(useDigest): self._passwordType=self.PASSWORD_DIGEST_TYPE digest=hashlib.new("sha",self._nonce+self._created+password).digest() 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", "true") # create element timestampElem = securityElem.createAppendElement("", "wsse:Timestamp") timestampElem.node.setAttribute("xmlns:wsse", self.UTIL_NS) # create element createdElem = timestampElem.createAppendElement("", "wsse:Created") createdElem.node.setAttribute("xmlns:wsse", self.UTIL_NS) createdElem.createAppendTextNode(self._created) # 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) # 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]) self.log.debug(soapWriter) def verify(self,soapWriter): self
Out of context snippet for connection
self.baseUrl=baseUrl locator = Context_WSLocator() self.sigHandler = SignatureHandler("session","nosession",False) self.port=locator.getContext_WSPortType(baseUrl) self.port.binding.sig_handler=self.sigHandler request = getServerVersionRequest() response = self.port.getServerVersion(request) ret = response._return self.log.info("Connecting to ContextWS version: %s" % ret._version) request = initializeVersion2Request() response = self.port.initializeVersion2(request) sessionId = response._return self.sigHandler = SignatureHandler("session",sessionId,False) self.port.binding.sig_handler=self.sigHandler self.log.info("Received sessionId:%s from initializeVersion2 and set as password" %sessionId) self.log.info("Should be all set to use other functions!") self.loginTool("venderId","programId","sharedSecret") ..... def loginTool(self,vendorId,programId,sharedSecret): self.log.debug("loginTool") request = loginToolRequest() request._password = sharedSecret request._clientVendorId = vendorId request._clientProgramId = programId response = self.port.loginTool(request) return response._return
No Comments »
RSS feed for comments on this post. TrackBack URL
Leave a comment
You must be logged in to post a comment.

