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
VN:F [1.9.13_1145]
Rating: 7.8/10 (6 votes cast)
VN:F [1.9.13_1145]
Rating: +3 (from 5 votes)