Changeset 6565 for TI12-security


Ignore:
Timestamp:
12/02/10 13:55:55 (10 years ago)
Author:
pjkersha
Message:

Refactoring SAML SOAP bindings module to include AuthzDecisionQuery?

Location:
TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/saml_utils
Files:
1 added
1 deleted
2 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/saml_utils/bindings/soap/__init__.py

    r6564 r6565  
    276276    response = property(fget=_getResponse, fset=_setResponse,  
    277277                        doc="SAML Response associated with this exception") 
    278  
    279  
    280 class AttributeQueryResponseError(SubjectQueryResponseError): 
    281     """SAML Response error from Attribute Query""" 
    282      
    283      
    284 class SubjectQuerySOAPBinding(SOAPBinding):  
    285     """SAML Subject Query SOAP Binding 
    286      
    287     Nb. Assumes X.509 subject type for query issuer 
    288     """ 
    289     SUBJECT_ID_OPTNAME = 'subjectID' 
    290     ISSUER_NAME_OPTNAME = 'issuerName' 
    291     CLOCK_SKEW_OPTNAME = 'clockSkewTolerance' 
    292     VERIFY_TIME_CONDITIONS_OPTNAME = 'verifyTimeConditions' 
    293      
    294     CONFIG_FILE_OPTNAMES = ( 
    295         SUBJECT_ID_OPTNAME, 
    296         ISSUER_NAME_OPTNAME,                  
    297         CLOCK_SKEW_OPTNAME, 
    298         VERIFY_TIME_CONDITIONS_OPTNAME             
    299     ) 
    300      
    301     __PRIVATE_ATTR_PREFIX = "__" 
    302     __slots__ = tuple([__PRIVATE_ATTR_PREFIX + i  
    303                        for i in CONFIG_FILE_OPTNAMES]) 
    304     del i 
    305      
    306     def __init__(self, **kw): 
    307         '''Create SOAP Client for SAML Attribute Query''' 
    308         self.__issuerName = None 
    309         self.__clockSkewTolerance = timedelta(seconds=0.) 
    310         self.__verifyTimeConditions = True 
    311          
    312         super(SubjectQuerySOAPBinding, self).__init__(**kw) 
    313  
    314     def _getVerifyTimeConditions(self): 
    315         return self.__verifyTimeConditions 
    316  
    317     def _setVerifyTimeConditions(self, value): 
    318         if isinstance(value, bool): 
    319             self.__verifyTimeConditions = value 
    320              
    321         if isinstance(value, basestring): 
    322             self.__verifyTimeConditions = str2Bool(value) 
    323         else: 
    324             raise TypeError('Expecting bool or string type for ' 
    325                             '"verifyTimeConditions"; got %r instead' %  
    326                             type(value)) 
    327  
    328     verifyTimeConditions = property(_getVerifyTimeConditions,  
    329                                     _setVerifyTimeConditions,  
    330                                     doc='Set to True to verify any time ' 
    331                                         'Conditions set in the returned ' 
    332                                         'response assertions') 
    333          
    334     def _getSubjectID(self): 
    335         return self.__subjectID 
    336  
    337     def _setSubjectID(self, value): 
    338         if not isinstance(value, basestring): 
    339             raise TypeError('Expecting string type for "subjectID"; got %r ' 
    340                             'instead' % type(value)) 
    341         self.__subjectID = value 
    342  
    343     subjectID = property(_getSubjectID, _setSubjectID,  
    344                          doc="ID to be sent as query subject")   
    345  
    346     def _getIssuerName(self): 
    347         return self.__issuerName 
    348  
    349     def _setIssuerName(self, value): 
    350         if not isinstance(value, basestring): 
    351             raise TypeError('Expecting string type for "issuerName"; ' 
    352                             'got %r instead' % type(value)) 
    353              
    354         self.__issuerName = value 
    355  
    356     issuerName = property(_getIssuerName, _setIssuerName,  
    357                         doc="Distinguished Name of issuer of SAML Attribute " 
    358                             "Query to Attribute Authority") 
    359  
    360     def _getClockSkewTolerance(self): 
    361         return self.__clockSkewTolerance 
    362  
    363     def _setClockSkewTolerance(self, value): 
    364         if isinstance(value, (float, int, long)): 
    365             self.__clockSkewTolerance = timedelta(seconds=value) 
    366              
    367         elif isinstance(value, basestring): 
    368             self.__clockSkewTolerance = timedelta(seconds=float(value)) 
    369         else: 
    370             raise TypeError('Expecting float, int, long or string type for ' 
    371                             '"clockSkewTolerance"; got %r' % type(value)) 
    372  
    373     clockSkewTolerance = property(fget=_getClockSkewTolerance,  
    374                                   fset=_setClockSkewTolerance,  
    375                                   doc="Allow a tolerance in seconds for SAML " 
    376                                       "Query issueInstant parameter check and " 
    377                                       "assertion condition notBefore and " 
    378                                       "notOnOrAfter times to allow for clock " 
    379                                       "skew")   
    380  
    381     def _createQuery(self, queryClass=SubjectQuery): 
    382         """ Create a SAML SubjectQuery derived type instance 
    383         @param queryClass: query type to create - must be  
    384         saml.saml2.core.SubjectQuery type 
    385         @type queryClass: type 
    386         @return: query instance 
    387         @rtype: saml.saml2.core.SubjectQuery 
    388         """ 
    389         if not isinstance(queryClass, SubjectQuery): 
    390             raise TypeError('Query class %r is not a SubjectQuery derived type' 
    391                             % queryClass) 
    392              
    393         query = queryClass() 
    394         query.version = SAMLVersion(SAMLVersion.VERSION_20) 
    395         query.id = str(uuid4()) 
    396         query.issueInstant = datetime.utcnow() 
    397          
    398         if self.issuerName is None: 
    399             raise AttributeError('No issuer DN has been set for SAML Query') 
    400          
    401         query.issuer = Issuer() 
    402         query.issuer.format = Issuer.X509_SUBJECT 
    403         query.issuer.value = self.issuerName 
    404                          
    405         query.subject = Subject()   
    406         query.subject.nameID = NameID() 
    407         query.subject.nameID.format = EsgSamlNamespaces.NAMEID_FORMAT 
    408         query.subject.nameID.value = self.subjectID 
    409              
    410         return query 
    411  
    412     def send(self, **kw): 
    413         '''Make an attribute query to a remote SAML service 
    414          
    415         @type uri: basestring  
    416         @param uri: uri of service.  May be omitted if set from request.url 
    417         @type request: ndg.security.common.soap.UrlLib2SOAPRequest 
    418         @param request: SOAP request object to which query will be attached 
    419         defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest 
    420         ''' 
    421         query = self._createQuery() 
    422              
    423         response = super(SubjectQuerySOAPBinding, self).send(query, **kw) 
    424  
    425         # Perform validation 
    426         if response.status.statusCode.value != StatusCode.SUCCESS_URI: 
    427             msg = ('Return status code flagged an error.  The message is: %r' % 
    428                    response.status.statusMessage.value) 
    429             samlRespError = SubjectQueryResponseError(msg) 
    430             samlRespError.response = response 
    431             raise samlRespError 
    432          
    433         # Check Query ID matches the query ID the service received 
    434         if response.inResponseTo != query.id: 
    435             msg = ('Response in-response-to ID %r, doesn\'t match the original ' 
    436                    'query ID, %r' % (response.inResponseTo, query.id)) 
    437              
    438             samlRespError = SubjectQueryResponseError(msg) 
    439             samlRespError.response = response 
    440             raise samlRespError 
    441          
    442         utcNow = datetime.utcnow() + self.clockSkewTolerance 
    443         if response.issueInstant > utcNow: 
    444             msg = ('SAML Attribute Response issueInstant [%s] is after ' 
    445                    'the current clock time [%s]' %  
    446                    (query.issueInstant, SAMLDateTime.toString(utcNow))) 
    447              
    448             samlRespError = SubjectQueryResponseError(msg)                   
    449             samlRespError.response = response 
    450             raise samlRespError 
    451          
    452         for assertion in response.assertions: 
    453             if self.verifyTimeConditions and assertion.conditions is not None: 
    454                 if utcNow < assertion.conditions.notBefore:             
    455                     msg = ('The current clock time [%s] is before the SAML ' 
    456                            'Attribute Response assertion conditions not before ' 
    457                            'time [%s]' %  
    458                            (SAMLDateTime.toString(utcNow), 
    459                             assertion.conditions.notBefore)) 
    460                                
    461                     samlRespError = SubjectQueryResponseError(msg) 
    462                     samlRespError.response = response 
    463                     raise samlRespError 
    464                   
    465                 if utcNow >= assertion.conditions.notOnOrAfter:            
    466                     msg = ('The current clock time [%s] is on or after the ' 
    467                            'SAML Attribute Response assertion conditions not ' 
    468                            'on or after time [%s]' %  
    469                            (SAMLDateTime.toString(utcNow), 
    470                             response.assertion.conditions.notOnOrAfter)) 
    471                      
    472                     samlRespError = SubjectQueryResponseError(msg)  
    473                     samlRespError.response = response 
    474                     raise samlRespError    
    475              
    476         return response  
    477  
    478  
    479 class AttributeQuerySOAPBinding(SOAPBinding):  
    480     """SAML Attribute Query SOAP Binding 
    481      
    482     Nb. Assumes X.509 subject type for query issuer 
    483     """ 
    484     SUBJECT_ID_OPTNAME = 'subjectID' 
    485     ISSUER_NAME_OPTNAME = 'issuerName' 
    486     CLOCK_SKEW_OPTNAME = 'clockSkewTolerance' 
    487      
    488     CONFIG_FILE_OPTNAMES = ( 
    489         SUBJECT_ID_OPTNAME, 
    490         ISSUER_NAME_OPTNAME,                  
    491         CLOCK_SKEW_OPTNAME             
    492     ) 
    493      
    494     QUERY_ATTRIBUTES_ATTRNAME = 'queryAttributes' 
    495     LEN_QUERY_ATTRIBUTES_ATTRNAME = len(QUERY_ATTRIBUTES_ATTRNAME) 
    496     QUERY_ATTRIBUTES_PAT = re.compile(',\s*') 
    497      
    498     __PRIVATE_ATTR_PREFIX = "__" 
    499     __slots__ = tuple([__PRIVATE_ATTR_PREFIX + i  
    500                        for i in \ 
    501                        CONFIG_FILE_OPTNAMES + (QUERY_ATTRIBUTES_ATTRNAME,)]) 
    502     del i 
    503      
    504     def __init__(self, **kw): 
    505         '''Create SOAP Client for SAML Attribute Query''' 
    506         self.__issuerName = None 
    507         self.__queryAttributes = TypedList(Attribute) 
    508         self.__clockSkewTolerance = timedelta(seconds=0.) 
    509                  
    510         super(AttributeQuerySOAPBinding, self).__init__(**kw) 
    511  
    512     @classmethod 
    513     def fromConfig(cls, cfg, **kw): 
    514         '''Alternative constructor makes object from config file settings 
    515         @type cfg: basestring /ConfigParser derived type 
    516         @param cfg: configuration file path or ConfigParser type object 
    517         @rtype: ndg.security.common.credentialWallet.AttributeQuery 
    518         @return: new instance of this class 
    519         ''' 
    520         obj = cls() 
    521         obj.parseConfig(cfg, **kw) 
    522          
    523         return obj 
    524  
    525     def parseConfig(self, cfg, prefix='', section='DEFAULT'): 
    526         '''Read config file settings 
    527         @type cfg: basestring /ConfigParser derived type 
    528         @param cfg: configuration file path or ConfigParser type object 
    529         @type prefix: basestring 
    530         @param prefix: prefix for option names e.g. "attributeQuery." 
    531         @type section: baestring 
    532         @param section: configuration file section from which to extract 
    533         parameters. 
    534         '''   
    535         if isinstance(cfg, basestring): 
    536             cfgFilePath = path.expandvars(cfg) 
    537             _cfg = CaseSensitiveConfigParser() 
    538             _cfg.read(cfgFilePath) 
    539              
    540         elif isinstance(cfg, ConfigParser): 
    541             _cfg = cfg    
    542         else: 
    543             raise AttributeError('Expecting basestring or ConfigParser type ' 
    544                                  'for "cfg" attribute; got %r type' % type(cfg)) 
    545          
    546         prefixLen = len(prefix) 
    547         for optName, val in _cfg.items(section): 
    548             if prefix: 
    549                 # Filter attributes based on prefix 
    550                 if optName.startswith(prefix): 
    551                     setattr(self, optName[prefixLen:], val) 
    552             else: 
    553                 # No prefix set - attempt to set all attributes    
    554                 setattr(self, optName, val) 
    555              
    556     def __setattr__(self, name, value): 
    557         """Enable setting of SAML query attribute objects via a comma separated 
    558         string suitable for use reading from an ini file.   
    559         """ 
    560         try: 
    561             super(AttributeQuerySOAPBinding, self).__setattr__(name, value) 
    562              
    563         except AttributeError: 
    564             if name.startswith( 
    565                         AttributeQuerySOAPBinding.QUERY_ATTRIBUTES_ATTRNAME): 
    566                 # Special handler for parsing string format settings 
    567                 if not isinstance(value, basestring): 
    568                     raise TypeError('Expecting string format for special ' 
    569                                     '%r attribute; got %r instead' % 
    570                                     (name, type(value))) 
    571                      
    572                 pat = AttributeQuerySOAPBinding.QUERY_ATTRIBUTES_PAT 
    573                 attribute = Attribute() 
    574                  
    575                 (attribute.name,  
    576                  attribute.friendlyName,  
    577                  attribute.nameFormat) = pat.split(value) 
    578                   
    579                 self.queryAttributes.append(attribute) 
    580             else: 
    581                 raise 
    582  
    583     def _getSubjectID(self): 
    584         return self.__subjectID 
    585  
    586     def _setSubjectID(self, value): 
    587         if not isinstance(value, basestring): 
    588             raise TypeError('Expecting string type for "subjectID"; got %r ' 
    589                             'instead' % type(value)) 
    590         self.__subjectID = value 
    591  
    592     subjectID = property(_getSubjectID, _setSubjectID,  
    593                          doc="ID to be sent as query subject")   
    594               
    595     def _getQueryAttributes(self): 
    596         """Returns a *COPY* of the attributes to avoid overwriting the  
    597         member variable content 
    598         """ 
    599         return self.__queryAttributes 
    600  
    601     def _setQueryAttributes(self, value): 
    602         if not isinstance(value, TypedList) and value.elementType != Attribute: 
    603             raise TypeError('Expecting TypedList(Attribute) type for ' 
    604                             '"queryAttributes"; got %r instead' % type(value))  
    605          
    606         self.__queryAttributes = value 
    607      
    608     queryAttributes = property(_getQueryAttributes,  
    609                                _setQueryAttributes,  
    610                                doc="List of attributes to query from the " 
    611                                    "Attribute Authority") 
    612  
    613     def _getIssuerName(self): 
    614         return self.__issuerName 
    615  
    616     def _setIssuerName(self, value): 
    617         if not isinstance(value, basestring): 
    618             raise TypeError('Expecting string type for "issuerName"; ' 
    619                             'got %r instead' % type(value)) 
    620              
    621         self.__issuerName = value 
    622  
    623     issuerName = property(_getIssuerName, _setIssuerName,  
    624                         doc="Distinguished Name of issuer of SAML Attribute " 
    625                             "Query to Attribute Authority") 
    626  
    627     def _getClockSkewTolerance(self): 
    628         return self.__clockSkewTolerance 
    629  
    630     def _setClockSkewTolerance(self, value): 
    631         if isinstance(value, (float, int, long)): 
    632             self.__clockSkewTolerance = timedelta(seconds=value) 
    633              
    634         elif isinstance(value, basestring): 
    635             self.__clockSkewTolerance = timedelta(seconds=float(value)) 
    636         else: 
    637             raise TypeError('Expecting float, int, long or string type for ' 
    638                             '"clockSkewTolerance"; got %r' % type(value)) 
    639  
    640     clockSkewTolerance = property(fget=_getClockSkewTolerance,  
    641                          fset=_setClockSkewTolerance,  
    642                          doc="Allow a clock skew in seconds for SAML Attribute" 
    643                              " Query issueInstant parameter check")   
    644  
    645     def _createQuery(self): 
    646         """ Create a SAML attribute query""" 
    647         attributeQuery = AttributeQuery() 
    648         attributeQuery.version = SAMLVersion(SAMLVersion.VERSION_20) 
    649         attributeQuery.id = str(uuid4()) 
    650         attributeQuery.issueInstant = datetime.utcnow() 
    651          
    652         if self.issuerName is None: 
    653             raise AttributeError('No issuer DN has been set for SAML Attribute ' 
    654                                  'Query') 
    655          
    656         attributeQuery.issuer = Issuer() 
    657         attributeQuery.issuer.format = Issuer.X509_SUBJECT 
    658         attributeQuery.issuer.value = self.issuerName 
    659                          
    660         attributeQuery.subject = Subject()   
    661         attributeQuery.subject.nameID = NameID() 
    662         attributeQuery.subject.nameID.format = EsgSamlNamespaces.NAMEID_FORMAT 
    663         attributeQuery.subject.nameID.value = self.subjectID 
    664                    
    665         # Add list of attributes to query                       
    666         for attribute in self.queryAttributes: 
    667             attributeQuery.attributes.append(attribute) 
    668              
    669         return attributeQuery 
    670  
    671     def send(self, **kw): 
    672         '''Make an attribute query to a remote SAML service 
    673          
    674         @type uri: basestring  
    675         @param uri: uri of service.  May be omitted if set from request.url 
    676         @type request: ndg.security.common.soap.UrlLib2SOAPRequest 
    677         @param request: SOAP request object to which query will be attached 
    678         defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest 
    679         ''' 
    680         attributeQuery = self._createQuery() 
    681              
    682         response = super(AttributeQuerySOAPBinding, self).send(attributeQuery,  
    683                                                                **kw) 
    684  
    685         # Perform validation 
    686         if response.status.statusCode.value != StatusCode.SUCCESS_URI: 
    687             msg = ('Return status code flagged an error.  The message is: %r' % 
    688                    response.status.statusMessage.value) 
    689             samlRespError = AttributeQueryResponseError(msg) 
    690             samlRespError.response = response 
    691             raise samlRespError 
    692          
    693         # Check Query ID matches the query ID the service received 
    694         if response.inResponseTo != attributeQuery.id: 
    695             msg = ('Response in-response-to ID %r, doesn\'t match the original ' 
    696                    'query ID, %r' % (response.inResponseTo, attributeQuery.id)) 
    697              
    698             samlRespError = AttributeQueryResponseError(msg) 
    699             samlRespError.response = response 
    700             raise samlRespError 
    701          
    702         utcNow = datetime.utcnow() + self.clockSkewTolerance 
    703         if response.issueInstant > utcNow: 
    704             msg = ('SAML Attribute Response issueInstant [%s] is after ' 
    705                    'the current clock time [%s]' %  
    706                    (attributeQuery.issueInstant, SAMLDateTime.toString(utcNow))) 
    707              
    708             samlRespError = AttributeQueryResponseError(msg)                   
    709             samlRespError.response = response 
    710             raise samlRespError 
    711          
    712         for assertion in response.assertions: 
    713             if assertion.conditions is not None: 
    714                 if utcNow < assertion.conditions.notBefore:             
    715                     msg = ('The current clock time [%s] is before the SAML ' 
    716                            'Attribute Response assertion conditions not before ' 
    717                            'time [%s]' %  
    718                            (SAMLDateTime.toString(utcNow), 
    719                             assertion.conditions.notBefore)) 
    720                                
    721                     samlRespError = AttributeQueryResponseError(msg) 
    722                     samlRespError.response = response 
    723                     raise samlRespError 
    724                   
    725                 if utcNow >= assertion.conditions.notOnOrAfter:            
    726                     msg = ('The current clock time [%s] is on or after the ' 
    727                            'SAML Attribute Response assertion conditions not ' 
    728                            'on or after time [%s]' %  
    729                            (SAMLDateTime.toString(utcNow), 
    730                             response.assertion.conditions.notOnOrAfter)) 
    731                      
    732                     samlRespError = AttributeQueryResponseError(msg)  
    733                     samlRespError.response = response 
    734                     raise samlRespError    
    735              
    736         return response  
    737  
    738      
    739 class AttributeQuerySslSOAPBinding(AttributeQuerySOAPBinding): 
    740     """Specialisation of AttributeQuerySOAPbinding taking in the setting of 
    741     SSL parameters for mutual authentication 
    742     """ 
    743     SSL_CONTEXT_PROXY_SUPPORT = _sslContextProxySupport 
    744     __slots__ = ('__sslCtxProxy',) 
    745      
    746     def __init__(self, **kw): 
    747         if not AttributeQuerySslSOAPBinding.SSL_CONTEXT_PROXY_SUPPORT: 
    748             raise ImportError("ndg.security.common.utils.m2crypto import " 
    749                               "failed - missing M2Crypto package?") 
    750          
    751         # Miss out default HTTPSHandler and set in send() instead 
    752         if 'handlers' in kw: 
    753             raise TypeError("__init__() got an unexpected keyword argument " 
    754                             "'handlers'") 
    755              
    756         super(AttributeQuerySslSOAPBinding, self).__init__(handlers=(), **kw) 
    757         self.__sslCtxProxy = SSLContextProxy() 
    758  
    759     def send(self, **kw): 
    760         """Override base class implementation to pass explicit SSL Context 
    761         """ 
    762         httpsHandler = HTTPSHandler(ssl_context=self.sslCtxProxy.createCtx()) 
    763         self.client.openerDirector.add_handler(httpsHandler) 
    764         return super(AttributeQuerySslSOAPBinding, self).send(**kw) 
    765          
    766     @property 
    767     def sslCtxProxy(self): 
    768         """SSL Context Proxy object used for setting up an SSL Context for 
    769         queries 
    770         """ 
    771         return self.__sslCtxProxy 
    772              
    773     def __setattr__(self, name, value): 
    774         """Enable setting of SSLContextProxy attributes as if they were  
    775         attributes of this class.  This is intended as a convenience for  
    776         making settings parameters read from a config file 
    777         """ 
    778         try: 
    779             super(AttributeQuerySslSOAPBinding, self).__setattr__(name, value) 
    780              
    781         except AttributeError: 
    782             # Coerce into setting SSL Context Proxy attributes 
    783             try: 
    784                 setattr(self.sslCtxProxy, name, value) 
    785             except: 
    786                 raise 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/saml_utils/bindings/soap/attributequery.py

    r6564 r6565  
    1111import logging 
    1212log = logging.getLogger(__name__) 
     13 
     14from ndg.security.common.sam_utils.binding.soap.subjectquery import ( 
     15                                                    SubjectQuery, 
     16                                                    SubjectQueryResponseError) 
     17 
     18 
     19class AttributeQueryResponseError(SubjectQueryResponseError): 
     20    """SAML Response error from Attribute Query""" 
     21     
     22 
     23class AttributeQuerySOAPBinding(SOAPBinding):  
     24    """SAML Attribute Query SOAP Binding 
     25     
     26    Nb. Assumes X.509 subject type for query issuer 
     27    """ 
     28    SUBJECT_ID_OPTNAME = 'subjectID' 
     29    ISSUER_NAME_OPTNAME = 'issuerName' 
     30    CLOCK_SKEW_OPTNAME = 'clockSkewTolerance' 
     31     
     32    CONFIG_FILE_OPTNAMES = ( 
     33        SUBJECT_ID_OPTNAME, 
     34        ISSUER_NAME_OPTNAME,                  
     35        CLOCK_SKEW_OPTNAME             
     36    ) 
     37     
     38    QUERY_ATTRIBUTES_ATTRNAME = 'queryAttributes' 
     39    LEN_QUERY_ATTRIBUTES_ATTRNAME = len(QUERY_ATTRIBUTES_ATTRNAME) 
     40    QUERY_ATTRIBUTES_PAT = re.compile(',\s*') 
     41     
     42    __PRIVATE_ATTR_PREFIX = "__" 
     43    __slots__ = tuple([__PRIVATE_ATTR_PREFIX + i  
     44                       for i in \ 
     45                       CONFIG_FILE_OPTNAMES + (QUERY_ATTRIBUTES_ATTRNAME,)]) 
     46    del i 
     47     
     48    def __init__(self, **kw): 
     49        '''Create SOAP Client for SAML Attribute Query''' 
     50        self.__issuerName = None 
     51        self.__queryAttributes = TypedList(Attribute) 
     52        self.__clockSkewTolerance = timedelta(seconds=0.) 
     53                 
     54        super(AttributeQuerySOAPBinding, self).__init__(**kw) 
     55 
     56    @classmethod 
     57    def fromConfig(cls, cfg, **kw): 
     58        '''Alternative constructor makes object from config file settings 
     59        @type cfg: basestring /ConfigParser derived type 
     60        @param cfg: configuration file path or ConfigParser type object 
     61        @rtype: ndg.security.common.credentialWallet.AttributeQuery 
     62        @return: new instance of this class 
     63        ''' 
     64        obj = cls() 
     65        obj.parseConfig(cfg, **kw) 
     66         
     67        return obj 
     68 
     69    def parseConfig(self, cfg, prefix='', section='DEFAULT'): 
     70        '''Read config file settings 
     71        @type cfg: basestring /ConfigParser derived type 
     72        @param cfg: configuration file path or ConfigParser type object 
     73        @type prefix: basestring 
     74        @param prefix: prefix for option names e.g. "attributeQuery." 
     75        @type section: baestring 
     76        @param section: configuration file section from which to extract 
     77        parameters. 
     78        '''   
     79        if isinstance(cfg, basestring): 
     80            cfgFilePath = path.expandvars(cfg) 
     81            _cfg = CaseSensitiveConfigParser() 
     82            _cfg.read(cfgFilePath) 
     83             
     84        elif isinstance(cfg, ConfigParser): 
     85            _cfg = cfg    
     86        else: 
     87            raise AttributeError('Expecting basestring or ConfigParser type ' 
     88                                 'for "cfg" attribute; got %r type' % type(cfg)) 
     89         
     90        prefixLen = len(prefix) 
     91        for optName, val in _cfg.items(section): 
     92            if prefix: 
     93                # Filter attributes based on prefix 
     94                if optName.startswith(prefix): 
     95                    setattr(self, optName[prefixLen:], val) 
     96            else: 
     97                # No prefix set - attempt to set all attributes    
     98                setattr(self, optName, val) 
     99             
     100    def __setattr__(self, name, value): 
     101        """Enable setting of SAML query attribute objects via a comma separated 
     102        string suitable for use reading from an ini file.   
     103        """ 
     104        try: 
     105            super(AttributeQuerySOAPBinding, self).__setattr__(name, value) 
     106             
     107        except AttributeError: 
     108            if name.startswith( 
     109                        AttributeQuerySOAPBinding.QUERY_ATTRIBUTES_ATTRNAME): 
     110                # Special handler for parsing string format settings 
     111                if not isinstance(value, basestring): 
     112                    raise TypeError('Expecting string format for special ' 
     113                                    '%r attribute; got %r instead' % 
     114                                    (name, type(value))) 
     115                     
     116                pat = AttributeQuerySOAPBinding.QUERY_ATTRIBUTES_PAT 
     117                attribute = Attribute() 
     118                 
     119                (attribute.name,  
     120                 attribute.friendlyName,  
     121                 attribute.nameFormat) = pat.split(value) 
     122                  
     123                self.queryAttributes.append(attribute) 
     124            else: 
     125                raise 
     126 
     127    def _getSubjectID(self): 
     128        return self.__subjectID 
     129 
     130    def _setSubjectID(self, value): 
     131        if not isinstance(value, basestring): 
     132            raise TypeError('Expecting string type for "subjectID"; got %r ' 
     133                            'instead' % type(value)) 
     134        self.__subjectID = value 
     135 
     136    subjectID = property(_getSubjectID, _setSubjectID,  
     137                         doc="ID to be sent as query subject")   
     138              
     139    def _getQueryAttributes(self): 
     140        """Returns a *COPY* of the attributes to avoid overwriting the  
     141        member variable content 
     142        """ 
     143        return self.__queryAttributes 
     144 
     145    def _setQueryAttributes(self, value): 
     146        if not isinstance(value, TypedList) and value.elementType != Attribute: 
     147            raise TypeError('Expecting TypedList(Attribute) type for ' 
     148                            '"queryAttributes"; got %r instead' % type(value))  
     149         
     150        self.__queryAttributes = value 
     151     
     152    queryAttributes = property(_getQueryAttributes,  
     153                               _setQueryAttributes,  
     154                               doc="List of attributes to query from the " 
     155                                   "Attribute Authority") 
     156 
     157    def _getIssuerName(self): 
     158        return self.__issuerName 
     159 
     160    def _setIssuerName(self, value): 
     161        if not isinstance(value, basestring): 
     162            raise TypeError('Expecting string type for "issuerName"; ' 
     163                            'got %r instead' % type(value)) 
     164             
     165        self.__issuerName = value 
     166 
     167    issuerName = property(_getIssuerName, _setIssuerName,  
     168                        doc="Distinguished Name of issuer of SAML Attribute " 
     169                            "Query to Attribute Authority") 
     170 
     171    def _getClockSkewTolerance(self): 
     172        return self.__clockSkewTolerance 
     173 
     174    def _setClockSkewTolerance(self, value): 
     175        if isinstance(value, (float, int, long)): 
     176            self.__clockSkewTolerance = timedelta(seconds=value) 
     177             
     178        elif isinstance(value, basestring): 
     179            self.__clockSkewTolerance = timedelta(seconds=float(value)) 
     180        else: 
     181            raise TypeError('Expecting float, int, long or string type for ' 
     182                            '"clockSkewTolerance"; got %r' % type(value)) 
     183 
     184    clockSkewTolerance = property(fget=_getClockSkewTolerance,  
     185                         fset=_setClockSkewTolerance,  
     186                         doc="Allow a clock skew in seconds for SAML Attribute" 
     187                             " Query issueInstant parameter check")   
     188 
     189    def _createQuery(self): 
     190        """ Create a SAML attribute query""" 
     191        attributeQuery = AttributeQuery() 
     192        attributeQuery.version = SAMLVersion(SAMLVersion.VERSION_20) 
     193        attributeQuery.id = str(uuid4()) 
     194        attributeQuery.issueInstant = datetime.utcnow() 
     195         
     196        if self.issuerName is None: 
     197            raise AttributeError('No issuer DN has been set for SAML Attribute ' 
     198                                 'Query') 
     199         
     200        attributeQuery.issuer = Issuer() 
     201        attributeQuery.issuer.format = Issuer.X509_SUBJECT 
     202        attributeQuery.issuer.value = self.issuerName 
     203                         
     204        attributeQuery.subject = Subject()   
     205        attributeQuery.subject.nameID = NameID() 
     206        attributeQuery.subject.nameID.format = EsgSamlNamespaces.NAMEID_FORMAT 
     207        attributeQuery.subject.nameID.value = self.subjectID 
     208                   
     209        # Add list of attributes to query                       
     210        for attribute in self.queryAttributes: 
     211            attributeQuery.attributes.append(attribute) 
     212             
     213        return attributeQuery 
     214 
     215    def send(self, **kw): 
     216        '''Make an attribute query to a remote SAML service 
     217         
     218        @type uri: basestring  
     219        @param uri: uri of service.  May be omitted if set from request.url 
     220        @type request: ndg.security.common.soap.UrlLib2SOAPRequest 
     221        @param request: SOAP request object to which query will be attached 
     222        defaults to ndg.security.common.soap.client.UrlLib2SOAPRequest 
     223        ''' 
     224        attributeQuery = self._createQuery() 
     225             
     226        response = super(AttributeQuerySOAPBinding, self).send(attributeQuery,  
     227                                                               **kw) 
     228 
     229        # Perform validation 
     230        if response.status.statusCode.value != StatusCode.SUCCESS_URI: 
     231            msg = ('Return status code flagged an error.  The message is: %r' % 
     232                   response.status.statusMessage.value) 
     233            samlRespError = AttributeQueryResponseError(msg) 
     234            samlRespError.response = response 
     235            raise samlRespError 
     236         
     237        # Check Query ID matches the query ID the service received 
     238        if response.inResponseTo != attributeQuery.id: 
     239            msg = ('Response in-response-to ID %r, doesn\'t match the original ' 
     240                   'query ID, %r' % (response.inResponseTo, attributeQuery.id)) 
     241             
     242            samlRespError = AttributeQueryResponseError(msg) 
     243            samlRespError.response = response 
     244            raise samlRespError 
     245         
     246        utcNow = datetime.utcnow() + self.clockSkewTolerance 
     247        if response.issueInstant > utcNow: 
     248            msg = ('SAML Attribute Response issueInstant [%s] is after ' 
     249                   'the current clock time [%s]' %  
     250                   (attributeQuery.issueInstant, SAMLDateTime.toString(utcNow))) 
     251             
     252            samlRespError = AttributeQueryResponseError(msg)                   
     253            samlRespError.response = response 
     254            raise samlRespError 
     255         
     256        for assertion in response.assertions: 
     257            if assertion.conditions is not None: 
     258                if utcNow < assertion.conditions.notBefore:             
     259                    msg = ('The current clock time [%s] is before the SAML ' 
     260                           'Attribute Response assertion conditions not before ' 
     261                           'time [%s]' %  
     262                           (SAMLDateTime.toString(utcNow), 
     263                            assertion.conditions.notBefore)) 
     264                               
     265                    samlRespError = AttributeQueryResponseError(msg) 
     266                    samlRespError.response = response 
     267                    raise samlRespError 
     268                  
     269                if utcNow >= assertion.conditions.notOnOrAfter:            
     270                    msg = ('The current clock time [%s] is on or after the ' 
     271                           'SAML Attribute Response assertion conditions not ' 
     272                           'on or after time [%s]' %  
     273                           (SAMLDateTime.toString(utcNow), 
     274                            response.assertion.conditions.notOnOrAfter)) 
     275                     
     276                    samlRespError = AttributeQueryResponseError(msg)  
     277                    samlRespError.response = response 
     278                    raise samlRespError    
     279             
     280        return response  
     281 
     282     
     283class AttributeQuerySslSOAPBinding(AttributeQuerySOAPBinding): 
     284    """Specialisation of AttributeQuerySOAPbinding taking in the setting of 
     285    SSL parameters for mutual authentication 
     286    """ 
     287    SSL_CONTEXT_PROXY_SUPPORT = _sslContextProxySupport 
     288    __slots__ = ('__sslCtxProxy',) 
     289     
     290    def __init__(self, **kw): 
     291        if not AttributeQuerySslSOAPBinding.SSL_CONTEXT_PROXY_SUPPORT: 
     292            raise ImportError("ndg.security.common.utils.m2crypto import " 
     293                              "failed - missing M2Crypto package?") 
     294         
     295        # Miss out default HTTPSHandler and set in send() instead 
     296        if 'handlers' in kw: 
     297            raise TypeError("__init__() got an unexpected keyword argument " 
     298                            "'handlers'") 
     299             
     300        super(AttributeQuerySslSOAPBinding, self).__init__(handlers=(), **kw) 
     301        self.__sslCtxProxy = SSLContextProxy() 
     302 
     303    def send(self, **kw): 
     304        """Override base class implementation to pass explicit SSL Context 
     305        """ 
     306        httpsHandler = HTTPSHandler(ssl_context=self.sslCtxProxy.createCtx()) 
     307        self.client.openerDirector.add_handler(httpsHandler) 
     308        return super(AttributeQuerySslSOAPBinding, self).send(**kw) 
     309         
     310    @property 
     311    def sslCtxProxy(self): 
     312        """SSL Context Proxy object used for setting up an SSL Context for 
     313        queries 
     314        """ 
     315        return self.__sslCtxProxy 
     316             
     317    def __setattr__(self, name, value): 
     318        """Enable setting of SSLContextProxy attributes as if they were  
     319        attributes of this class.  This is intended as a convenience for  
     320        making settings parameters read from a config file 
     321        """ 
     322        try: 
     323            super(AttributeQuerySslSOAPBinding, self).__setattr__(name, value) 
     324             
     325        except AttributeError: 
     326            # Coerce into setting SSL Context Proxy attributes 
     327            try: 
     328                setattr(self.sslCtxProxy, name, value) 
     329            except: 
     330                raise 
Note: See TracChangeset for help on using the changeset viewer.