Package ndg :: Package saml :: Package saml2 :: Package binding :: Package soap :: Package client
[hide private]

Source Code for Package ndg.saml.saml2.binding.soap.client

  1  """SAML 2.0 bindings module implements SOAP binding for attribute query 
  2   
  3  NERC DataGrid Project 
  4  """ 
  5  __author__ = "P J Kershaw" 
  6  __date__ = "02/09/09" 
  7  __copyright__ = "(C) 2009 Science and Technology Facilities Council" 
  8  __license__ = "http://www.apache.org/licenses/LICENSE-2.0" 
  9  __contact__ = "Philip.Kershaw@stfc.ac.uk" 
 10  __revision__ = '$Id: __init__.py 8049 2012-03-28 15:57:38Z pjkersha $' 
 11  import logging 
 12  log = logging.getLogger(__name__) 
 13   
 14  from os import path 
 15  from ConfigParser import ConfigParser, SafeConfigParser 
 16   
 17  from ndg.saml.common import SAMLObject 
 18   
 19  from ndg.saml.utils.factory import importModuleObject 
 20  from ndg.soap import SOAPEnvelopeBase 
 21  from ndg.soap.etree import SOAPEnvelope 
 22  from ndg.soap.client import (UrlLib2SOAPClient, UrlLib2SOAPRequest) 
 23   
 24  from ndg.saml.saml2.binding.soap import SOAPBindingInvalidResponse 
 25       
 26       
 27  _isIterable = lambda obj: getattr(obj, '__iter__', False)  
28 29 30 -class SOAPBinding(object):
31 '''Client SAML SOAP Binding''' 32 SOAP_ACTION = 'http://www.oasis-open.org/committees/security' 33 34 REQUEST_ENVELOPE_CLASS_OPTNAME = 'requestEnvelopeClass' 35 RESPONSE_ENVELOPE_CLASS_OPTNAME = 'responseEnvelopeClass' 36 SERIALISE_OPTNAME = 'serialise' 37 DESERIALISE_OPTNAME = 'deserialise' 38 39 CONFIG_FILE_OPTNAMES = ( 40 REQUEST_ENVELOPE_CLASS_OPTNAME, 41 RESPONSE_ENVELOPE_CLASS_OPTNAME, 42 SERIALISE_OPTNAME, 43 DESERIALISE_OPTNAME 44 ) 45 46 __PRIVATE_ATTR_PREFIX = "__" 47 __slots__ = tuple([__PRIVATE_ATTR_PREFIX + i 48 for i in CONFIG_FILE_OPTNAMES + ("client",)]) 49 del i 50 51 isIterable = staticmethod(_isIterable) 52
53 - def __init__(self, 54 requestEnvelopeClass=SOAPEnvelope, 55 responseEnvelopeClass=SOAPEnvelope, 56 serialise=None, 57 deserialise=None, 58 handlers=()):
59 '''Create SAML SOAP Client - Nb. serialisation functions must be set 60 before send()ing the request''' 61 self.__client = None 62 self.__serialise = None 63 self.__deserialise = None 64 65 if serialise is not None: 66 self.serialise = serialise 67 68 if deserialise is not None: 69 self.deserialise = deserialise 70 71 self.client = UrlLib2SOAPClient() 72 self.client.httpHeader['SOAPAction'] = SOAPBinding.SOAP_ACTION 73 74 # Configurable envelope classes 75 self.requestEnvelopeClass = requestEnvelopeClass 76 self.client.responseEnvelopeClass = responseEnvelopeClass 77 78 if not SOAPBinding.isIterable(handlers): 79 raise TypeError('Expecting iterable for "handlers" keyword; got %r' 80 % type(handlers)) 81 82 for handler in handlers: 83 self.client.openerDirector.add_handler(handler())
84
85 - def _getSerialise(self):
86 return self.__serialise
87
88 - def _setSerialise(self, value):
89 if isinstance(value, basestring): 90 self.__serialise = importModuleObject(value) 91 92 elif callable(value): 93 self.__serialise = value 94 else: 95 raise TypeError('Expecting callable for "serialise"; got %r' % 96 value)
97 98 serialise = property(_getSerialise, _setSerialise, 99 doc="callable to serialise request into XML type") 100
101 - def _getDeserialise(self):
102 return self.__deserialise
103
104 - def _setDeserialise(self, value):
105 if isinstance(value, basestring): 106 self.__deserialise = importModuleObject(value) 107 108 elif callable(value): 109 self.__deserialise = value 110 else: 111 raise TypeError('Expecting callable for "deserialise"; got %r' % 112 value)
113 114 115 deserialise = property(_getDeserialise, 116 _setDeserialise, 117 doc="callable to de-serialise response from XML " 118 "type") 119
120 - def _getRequestEnvelopeClass(self):
121 return self.__requestEnvelopeClass
122
123 - def _setRequestEnvelopeClass(self, value):
124 if isinstance(value, basestring): 125 self.client.responseEnvelopeClass = importModuleObject(value) 126 127 elif issubclass(value, SOAPEnvelopeBase): 128 self.client.responseEnvelopeClass = value 129 else: 130 raise TypeError('Expecting %r derived type or string for ' 131 '"requestEnvelopeClass" attribute; got %r' % 132 (SOAPEnvelopeBase, value)) 133 134 self.__requestEnvelopeClass = value
135 136 requestEnvelopeClass = property(_getRequestEnvelopeClass, 137 _setRequestEnvelopeClass, 138 doc="SOAP Envelope Request Class") 139
140 - def _getClient(self):
141 return self.__client
142
143 - def _setClient(self, value):
144 if not isinstance(value, UrlLib2SOAPClient): 145 raise TypeError('Expecting %r for "client"; got %r' % 146 (UrlLib2SOAPClient, type(value))) 147 self.__client = value
148 149 client = property(_getClient, _setClient, 150 doc="SOAP Client object") 151
152 - def send(self, samlObj, uri=None, request=None):
153 '''Make an request/query to a remote SAML service 154 155 @type samlObj: saml.common.SAMLObject 156 @param samlObj: SAML query/request object 157 @type uri: basestring 158 @param uri: uri of service. May be omitted if set from request.url 159 @type request: ndg.security.common.soap.UrlLib2SOAPRequest 160 @param request: SOAP request object to which query will be attached 161 defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest 162 ''' 163 if self.serialise is None: 164 raise AttributeError('No "serialise" method set to serialise the ' 165 'request') 166 167 if self.deserialise is None: 168 raise AttributeError('No "deserialise" method set to deserialise ' 169 'the response') 170 171 if not isinstance(samlObj, SAMLObject): 172 raise TypeError('Expecting %r for input attribute query; got %r' 173 % (SAMLObject, type(samlObj))) 174 175 if request is None: 176 request = UrlLib2SOAPRequest() 177 request.envelope = self.requestEnvelopeClass() 178 request.envelope.create() 179 180 if uri is not None: 181 request.url = uri 182 183 samlElem = self.serialise(samlObj) 184 185 # Attach query to SOAP body 186 request.envelope.body.elem.append(samlElem) 187 188 response = self.client.send(request) 189 190 if len(response.envelope.body.elem) != 1: 191 raise SOAPBindingInvalidResponse("Expecting single child element " 192 "is SOAP body") 193 194 response = self.deserialise(response.envelope.body.elem[0]) 195 196 return response
197 198 @classmethod
199 - def fromConfig(cls, cfg, **kw):
200 '''Alternative constructor makes object from config file settings 201 @type cfg: basestring / ConfigParser derived type 202 @param cfg: configuration file path or ConfigParser type object 203 @rtype: ndg.saml.saml2.binding.soap.client.SOAPBinding or derived type 204 @return: new instance of this class 205 ''' 206 obj = cls() 207 obj.parseConfig(cfg, **kw) 208 209 return obj
210
211 - def parseConfig(self, cfg, prefix='', section='DEFAULT'):
212 '''Read config file settings 213 @type cfg: basestring /ConfigParser derived type 214 @param cfg: configuration file path or ConfigParser type object 215 @type prefix: basestring 216 @param prefix: prefix for option names e.g. "attributeQuery." 217 @type section: baestring 218 @param section: configuration file section from which to extract 219 parameters. 220 ''' 221 if isinstance(cfg, basestring): 222 cfgFilePath = path.expandvars(cfg) 223 hereDir = path.dirname(cfgFilePath) 224 _cfg = SafeConfigParser(defaults=dict(here=hereDir)) 225 _cfg.optionxform = str 226 227 _cfg.read(cfgFilePath) 228 229 elif isinstance(cfg, ConfigParser): 230 _cfg = cfg 231 else: 232 raise AttributeError('Expecting basestring or ConfigParser type ' 233 'for "cfg" attribute; got %r type' % type(cfg)) 234 235 # Get items for this section as a dictionary so that parseKeywords can 236 # used to update the object 237 kw = dict(_cfg.items(section)) 238 if 'prefix' not in kw and prefix: 239 kw['prefix'] = prefix 240 241 self.parseKeywords(**kw)
242
243 - def parseKeywords(self, prefix='', **kw):
244 """Update object from input keywords 245 246 @type prefix: basestring 247 @param prefix: if a prefix is given, only update self from kw items 248 where keyword starts with this prefix 249 @type kw: dict 250 @param kw: items corresponding to class instance variables to 251 update. Keyword names must match their equivalent class instance 252 variable names. However, they may prefixed with <prefix> 253 """ 254 prefixLen = len(prefix) 255 for optName, val in kw.items(): 256 if prefix: 257 # Filter attributes based on prefix 258 if optName.startswith(prefix): 259 setattr(self, optName[prefixLen:], val) 260 else: 261 # No prefix set - attempt to set all attributes 262 setattr(self, optName, val)
263 264 @classmethod
265 - def fromKeywords(cls, prefix='', **kw):
266 """Create a new instance initialising instance variables from the 267 keyword inputs 268 @type prefix: basestring 269 @param prefix: if a prefix is given, only update self from kw items 270 where keyword starts with this prefix 271 @type kw: dict 272 @param kw: items corresponding to class instance variables to 273 update. Keyword names must match their equivalent class instance 274 variable names. However, they may prefixed with <prefix> 275 @return: new instance of this class 276 @rtype: ndg.saml.saml2.binding.soap.client.SOAPBinding or derived type 277 """ 278 obj = cls() 279 obj.fromKeywords(prefix=prefix, **kw) 280 281 return obj
282
283 - def __setattr__(self, name, value):
284 """Enable setting of SOAPBinding.client.responseEnvelopeClass as if it 285 were an attribute of self 286 """ 287 try: 288 super(SOAPBinding, self).__setattr__(name, value) 289 290 except AttributeError: 291 if 'name' == SOAPBinding.RESPONSE_ENVELOPE_CLASS_OPTNAME: 292 if isinstance(value, basestring): 293 self.client.responseEnvelopeClass = importModuleObject(value) 294 elif issubclass(value, SOAPEnvelopeBase): 295 self.client.responseEnvelopeClass = value 296 else: 297 raise TypeError('Expecting string or type instance for %r; ' 298 'got %r instead.' % (name, type(value))) 299 else: 300 raise
301
302 - def __getstate__(self):
303 '''Explicit implementation needed with __slots__''' 304 _dict = {} 305 for attrName in SOAPBinding.__slots__: 306 # Ugly hack to allow for derived classes setting private member 307 # variables 308 if attrName.startswith('__'): 309 attrName = "_SOAPBinding" + attrName 310 311 _dict[attrName] = getattr(self, attrName) 312 313 return _dict
314
315 - def __setstate__(self, attrDict):
316 '''Explicit implementation needed with __slots__''' 317 for attr, val in attrDict.items(): 318 setattr(self, attr, val)
319