Changeset 6044
- Timestamp:
- 24/11/09 17:02:57 (11 years ago)
- Location:
- TI12-security/trunk/python
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
TI12-security/trunk/python/ndg_security_common/ndg/security/common/credentialwallet.py
r6043 r6044 1795 1795 StatusCode, 1796 1796 StatusMessage) 1797 from saml.xml.etree import AssertionElementTree,ResponseElementTree1797 from saml.xml.etree import ResponseElementTree 1798 1798 1799 from ndg.security.common.saml_utils.bindings import SOAPBinding as SamlSoapBinding 1799 from ndg.security.common.saml_utils.bindings import SOAPBinding as \ 1800 SamlSoapBinding 1800 1801 from ndg.security.common.saml_utils.esg import EsgSamlNamespaces 1801 1802 from ndg.security.common.X509 import X500DN -
TI12-security/trunk/python/ndg_security_common/ndg/security/common/saml_utils/bindings.py
r6034 r6044 12 12 log = logging.getLogger(__name__) 13 13 14 import re 15 from datetime import datetime, timedelta 16 14 17 from M2Crypto.m2urllib2 import HTTPSHandler 15 18 16 from saml.saml2.core import Response, AttributeQuery 19 from saml.common import SAMLObject 20 from saml.utils import SAMLDateTime 21 from saml.saml2.core import Attribute, AttributeQuery, StatusCode, Response 17 22 from saml.xml.etree import AttributeQueryElementTree, ResponseElementTree 18 23 19 from ndg.security.common.utils.etree import QName 24 # Prevent whole module breaking if this is not available - it's only needed for 25 # AttributeQuerySslSOAPBinding 26 try: 27 from ndg.security.common.utils.m2crypto import SSLContextProxy 28 _sslContextProxySupport = True 29 30 except ImportError: 31 _sslContextProxySupport = False 32 33 from ndg.security.common.utils import TypedList 34 from ndg.security.common.utils.etree import QName 35 from ndg.security.common.X509 import X500DN 20 36 from ndg.security.common.soap import SOAPEnvelopeBase 21 37 from ndg.security.common.soap.etree import SOAPEnvelope 22 38 from ndg.security.common.soap.client import (UrlLib2SOAPClient, 23 UrlLib2SOAPRequest)39 UrlLib2SOAPRequest) 24 40 25 41 … … 36 52 37 53 class SOAPBinding(object): 38 '''Client SAML SOAP Binding for Attribute Query'''54 '''Client SAML SOAP Binding''' 39 55 40 56 isIterable = staticmethod(_isIterable) 57 __slots__ = ( 58 "client", 59 "requestEnvelopeClass", 60 "serialise", 61 "deserialise" 62 ) 63 __PRIVATE_ATTR_PREFIX = '_SOAPBinding__' 64 __slots__ += tuple([__PRIVATE_ATTR_PREFIX + i for i in __slots__]) 65 del i 41 66 42 67 def __init__(self, … … 44 69 responseEnvelopeClass=SOAPEnvelope, 45 70 handlers=(HTTPSHandler,)): 46 '''Create S OAP Client'''71 '''Create SAML SOAP Client''' 47 72 self.__client = None 48 73 … … 59 84 for handler in handlers: 60 85 self.client.openerDirector.add_handler(handler()) 61 62 self.serialise = AttributeQueryElementTree.toXML63 self.deserialise = ResponseElementTree.fromXML64 86 65 87 def _getSerialise(self): … … 114 136 client = property(_getClient, _setClient, 115 137 doc="SOAP Client object") 116 117 def attributeQuery(self, attributeQuery, uri=None, request=None): 138 139 def send(self, samlObj, uri=None, request=None): 140 '''Make an request/query to a remote SAML service 141 142 @type samlObj: saml.common.SAMLObject 143 @param samlObj: SAML query/request object 144 @type uri: basestring 145 @param uri: uri of service. May be omitted if set from request.url 146 @type request: ndg.security.common.soap.UrlLib2SOAPRequest 147 @param request: SOAP request object to which query will be attached 148 defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest 149 ''' 150 if not isinstance(samlObj, SAMLObject): 151 raise TypeError('Expecting %r for input attribute query; got %r' 152 % (SAMLObject, type(samlObj))) 153 154 if request is None: 155 request = UrlLib2SOAPRequest() 156 request.envelope = self.requestEnvelopeClass() 157 request.envelope.create() 158 159 if uri is not None: 160 request.url = uri 161 162 samlElem = self.serialise(samlObj) 163 164 # Attach query to SOAP body 165 request.envelope.body.elem.append(samlElem) 166 167 response = self.client.send(request) 168 169 if len(response.envelope.body.elem) != 1: 170 raise SOAPBindingInvalidResponse("Expecting single child element " 171 "is SOAP body") 172 173 if QName.getLocalPart(response.envelope.body.elem[0].tag)!='Response': 174 raise SOAPBindingInvalidResponse('Expecting "Response" element in ' 175 'SOAP body') 176 177 response = self.deserialise(response.envelope.body.elem[0]) 178 179 return response 180 181 def __getstate__(self): 182 '''Specific implementation needed with __slots__''' 183 return dict([(attrName, getattr(self, attrName)) 184 for attrName in self.__class__.__slots__]) 185 186 def __setstate__(self, attrDict): 187 '''Specific implementation needed with __slots__''' 188 for attr, val in attrDict.items(): 189 setattr(self, attr, val) 190 191 192 class AttributeQueryResponseError(SOAPBindingInvalidResponse): 193 """Attribute Authority returned a SAML Response error code""" 194 def __init__(self, *arg, **kw): 195 SOAPBindingInvalidResponse.__init__(self, *arg, **kw) 196 self.__response = None 197 198 def _getResponse(self): 199 '''Gets the response corresponding to this error 200 201 @return the response 202 ''' 203 return self.__response 204 205 def _setResponse(self, value): 206 '''Sets the response corresponding to this error. 207 208 @param value: the response 209 ''' 210 if not isinstance(value, Response): 211 raise TypeError('"response" must be a %r, got %r' % (Response, 212 type(value))) 213 self.__response = value 214 215 response = property(fget=_getResponse, fset=_setResponse, 216 doc="SAML Response associated with this exception") 217 218 219 class AttributeQuerySOAPBinding(SOAPBinding): 220 """SAML Attribute Query SOAP Binding""" 221 ISSUER_DN_OPTNAME = 'issuerDN' 222 CLOCK_SKEW_OPTNAME = 'clockSkew' 223 224 CONFIG_FILE_OPTNAMES = ( 225 ISSUER_DN_OPTNAME, 226 CLOCK_SKEW_OPTNAME 227 ) 228 229 QUERY_ATTRIBUTES_ATTRNAME = 'queryAttributes' 230 LEN_QUERY_ATTRIBUTES_ATTRNAME = len(QUERY_ATTRIBUTES_ATTRNAME) 231 QUERY_ATTRIBUTES_PAT = re.compile(',\s*') 232 233 __slots__ = ( 234 QUERY_ATTRIBUTES_ATTRNAME, 235 ) 236 __slots__ += CONFIG_FILE_OPTNAMES 237 __PRIVATE_ATTR_PREFIX = '_AttributeQuerySOAPBinding__' 238 __slots__ += tuple([__PRIVATE_ATTR_PREFIX + i for i in __slots__]) 239 del i 240 241 def __init__(self, **kw): 242 '''Create SOAP Client for SAML Attribute Query''' 243 self.__issuerDN = None 244 self.__queryAttributes = TypedList(Attribute) 245 self.__clockSkew = timedelta(seconds=0.) 246 247 super(AttributeQuerySOAPBinding, self).__init__(**kw) 248 249 self.serialise = AttributeQueryElementTree.toXML 250 self.deserialise = ResponseElementTree.fromXML 251 252 @classmethod 253 def fromConfig(cls, cfg, **kw): 254 '''Alternative constructor makes object from config file settings 255 @type cfg: basestring /ConfigParser derived type 256 @param cfg: configuration file path or ConfigParser type object 257 @rtype: ndg.security.common.credentialWallet.AttributeQuery 258 @return: new instance of this class 259 ''' 260 obj = cls() 261 obj.parseConfig(cfg, **kw) 262 263 return obj 264 265 def parseConfig(self, cfg, prefix='', section='DEFAULT'): 266 '''Read config file settings 267 @type cfg: basestring /ConfigParser derived type 268 @param cfg: configuration file path or ConfigParser type object 269 @type prefix: basestring 270 @param prefix: prefix for option names e.g. "attributeQuery." 271 @type section: baestring 272 @param section: configuration file section from which to extract 273 parameters. 274 ''' 275 if isinstance(cfg, basestring): 276 cfgFilePath = path.expandvars(cfg) 277 _cfg = CaseSensitiveConfigParser() 278 _cfg.read(cfgFilePath) 279 280 elif isinstance(cfg, ConfigParser): 281 _cfg = cfg 282 else: 283 raise AttributeError('Expecting basestring or ConfigParser type ' 284 'for "cfg" attribute; got %r type' % type(cfg)) 285 286 prefixLen = len(prefix) 287 for optName, val in _cfg.items(section): 288 if prefix and optName.startswith(prefix): 289 optName = optName[prefixLen:] 290 291 setattr(self, optName, val) 292 293 def __setattr__(self, name, value): 294 """Enable setting of SAML query attribute objects via a comma separated 295 string suitable for use reading from an ini file. 296 """ 297 try: 298 super(AttributeQuerySOAPBinding, self).__setattr__(name, value) 299 300 except AttributeError: 301 if name.startswith( 302 AttributeQuerySOAPBinding.QUERY_ATTRIBUTES_ATTRNAME): 303 # Special handler for parsing string format settings 304 if not isinstance(value, basestring): 305 raise TypeError('Expecting string format for special ' 306 '%r attribute; got %r instead' % 307 (name, type(value))) 308 309 pat = AttributeQuerySOAPBinding.QUERY_ATTRIBUTES_PAT 310 attribute = Attribute() 311 312 (attribute.name, 313 attribute.friendlyName, 314 attribute.format) = pat.split(value) 315 316 self.queryAttributes.append(attribute) 317 else: 318 raise 319 320 def _getQueryAttributes(self): 321 """Returns a *COPY* of the attributes to avoid overwriting the 322 member variable content 323 """ 324 return self.__queryAttributes 325 326 def _setQueryAttributes(self, value): 327 if not isinstance(value, TypedList) and value.elementType != Attribute: 328 raise TypeError('Expecting TypedList(Attribute) type for ' 329 '"queryAttributes"; got %r instead' % type(value)) 330 331 self.__queryAttributes = value 332 333 queryAttributes = property(_getQueryAttributes, 334 _setQueryAttributes, 335 doc="List of attributes to query from the " 336 "Attribute Authority") 337 338 def _getIssuerDN(self): 339 return self.__issuerDN 340 341 def _setIssuerDN(self, value): 342 if isinstance(value, basestring): 343 self.__issuerDN = X500DN.fromString(value) 344 345 elif isinstance(value, X500DN): 346 self.__issuerDN = value 347 else: 348 raise TypeError('Expecting string or X500DN type for "issuerDN"; ' 349 'got %r instead' % type(value)) 350 self.__issuerDN = value 351 352 issuerDN = property(_getIssuerDN, _setIssuerDN, 353 doc="Distinguished Name of issuer of SAML Attribute " 354 "Query to Attribute Authority") 355 356 def _getClockSkew(self): 357 return self.__clockSkew 358 359 def _setClockSkew(self, value): 360 if isinstance(value, (float, int, long)): 361 self.__clockSkew = timedelta(seconds=value) 362 363 elif isinstance(value, basestring): 364 self.__clockSkew = timedelta(seconds=float(value)) 365 else: 366 raise TypeError('Expecting float, int, long or string type for ' 367 '"clockSkew"; got %r' % type(value)) 368 369 clockSkew = property(fget=_getClockSkew, 370 fset=_setClockSkew, 371 doc="Allow a clock skew in seconds for SAML Attribute" 372 " Query issueInstant parameter check") 373 374 def send(self, attributeQuery, **kw): 118 375 '''Make an attribute query to a remote SAML service 119 376 … … 130 387 % (AttributeQuery, type(attributeQuery))) 131 388 132 if request is None: 133 request = UrlLib2SOAPRequest() 134 request.envelope = self.requestEnvelopeClass() 135 request.envelope.create() 136 137 if uri is not None: 138 request.url = uri 139 140 attributeQueryElem = self.serialise(attributeQuery) 141 142 # Attach query to SOAP body 143 request.envelope.body.elem.append(attributeQueryElem) 144 145 response = self.client.send(request) 146 147 if len(response.envelope.body.elem) != 1: 148 raise SOAPBindingInvalidResponse("Expecting single child element " 149 "is SOAP body") 150 151 if QName.getLocalPart(response.envelope.body.elem[0].tag)!='Response': 152 raise SOAPBindingInvalidResponse('Expecting "Response" element in ' 153 'SOAP body') 154 155 response = self.deserialise(response.envelope.body.elem[0]) 156 157 return response 158 389 response = super(AttributeQuerySOAPBinding, self).send(attributeQuery, 390 **kw) 391 392 # Perform validation 393 if response.status.statusCode.value != StatusCode.SUCCESS_URI: 394 msg = ('Return status code flagged an error. The message is: %r' % 395 response.status.statusMessage.value) 396 samlRespError = AttributeQueryResponseError(msg) 397 samlRespError.response = response 398 raise samlRespError 399 400 # Check Query ID matches the query ID the service received 401 if response.inResponseTo != attributeQuery.id: 402 msg = ('Response in-response-to ID %r, doesn\'t match the original ' 403 'query ID, %r' % (response.inResponseTo, attributeQuery.id)) 404 405 samlRespError = AttributeQueryResponseError(msg) 406 samlRespError.response = response 407 raise samlRespError 408 409 utcNow = datetime.utcnow() + self.clockSkew 410 if response.issueInstant > utcNow: 411 msg = ('SAML Attribute Response issueInstant [%s] is after ' 412 'the current clock time [%s]' % 413 (attributeQuery.issueInstant, SAMLDateTime.toString(utcNow))) 414 415 samlRespError = AttributeQueryResponseError(msg) 416 samlRespError.response = response 417 raise samlRespError 418 419 for assertion in response.assertions: 420 if utcNow < assertion.conditions.notBefore: 421 msg = ('The current clock time [%s] is before the SAML ' 422 'Attribute Response assertion conditions not before ' 423 'time [%s]' % 424 (SAMLDateTime.toString(utcNow), 425 assertion.conditions.notBefore)) 426 427 samlRespError = AttributeQueryResponseError(msg) 428 samlRespError.response = response 429 raise samlRespError 430 431 if utcNow >= assertion.conditions.notOnOrAfter: 432 msg = ('The current clock time [%s] is on or after the SAML ' 433 'Attribute Response assertion conditions not on or ' 434 'after time [%s]' % 435 (SAMLDateTime.toString(utcNow), 436 response.assertion.conditions.notOnOrAfter)) 437 438 samlRespError = AttributeQueryResponseError(msg) 439 samlRespError.response = response 440 raise samlRespError 441 442 443 class AttributeQuerySslSOAPBinding(AttributeQuerySOAPBinding): 444 """Specialisation of AttributeQuerySOAPbinding taking in the setting of 445 SSL parameters for mutual authentication 446 """ 447 SSL_CONTEXT_PROXY_SUPPORT = _sslContextProxySupport 448 449 def __init__(self, **kw): 450 if not AttributeQuerySslSOAPBinding.SSL_CONTEXT_PROXY_SUPPORT: 451 raise ImportError("ndg.security.common.utils.m2crypto import " 452 "failed - missing M2Crypto package?") 453 454 # Miss out default HTTPSHandler and set in send() instead 455 if 'handlers' in kw: 456 raise TypeError("__init__() got an unexpected keyword argument " 457 "'handlers'") 458 459 super(AttributeQuerySslSOAPBinding, self).__init__(handlers=(), **kw) 460 self.__sslCtxProxy = SSLContextProxy() 461 462 def send(self, **kw): 463 """Override base class implementation to pass explicit SSL Context 464 """ 465 httpsHandler = HTTPSHandler(ssl_context=self.sslCtxProxy.createCtx()) 466 self.client.openerDirector.add_handler(httpsHandler) 467 return super(AttributeQuerySslSOAPBinding, self).send(**kw) 468 469 @property 470 def sslCtxProxy(self): 471 """SSL Context Proxy object used for setting up an SSL Context for 472 queries 473 """ 474 return self.__sslCtxProxy 475 476 def __setattr__(self, name, value): 477 """Enable setting of SSLContextProxy attributes as if they were 478 attributes of this class. This is intended as a convenience for 479 making settings parameters read from a config file 480 """ 481 try: 482 super(AttributeQuerySOAPBinding, self).__setattr__(name, value) 483 484 except AttributeError: 485 # Coerce into setting SSL Context Proxy attributes 486 try: 487 setattr(self.sslCtxProxy, name, value) 488 except: 489 raise e -
TI12-security/trunk/python/ndg_security_test/ndg/security/test/unit/authz/msi/test_msi.py
r6043 r6044 15 15 PIPAttributeQuery, 16 16 PIPAttributeResponse) 17 17 18 18 19 class MsiBaseTestCase(BaseTestCase):
Note: See TracChangeset
for help on using the changeset viewer.