Changeset 6560


Ignore:
Timestamp:
12/02/10 13:44:53 (9 years ago)
Author:
pjkersha
Message:

Refactoring SAML SOAP bindings module to include AuthzDecisionQuery?

File:
1 edited

Legend:

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

    r6069 r6560  
    2727 
    2828from ndg.security.common.saml_utils.esg import EsgSamlNamespaces 
    29 from ndg.security.common.utils import TypedList 
     29from ndg.security.common.utils import TypedList, str2Bool 
    3030from ndg.security.common.utils.configfileparsers import ( 
    3131                                                    CaseSensitiveConfigParser) 
     
    4545except ImportError: 
    4646    _sslContextProxySupport = False 
    47  
    4847 
    4948 
     
    188187         
    189188        return response 
     189 
     190    @classmethod 
     191    def fromConfig(cls, cfg, **kw): 
     192        '''Alternative constructor makes object from config file settings 
     193        @type cfg: basestring /ConfigParser derived type 
     194        @param cfg: configuration file path or ConfigParser type object 
     195        @rtype: ndg.security.common.credentialWallet.AttributeQuery 
     196        @return: new instance of this class 
     197        ''' 
     198        obj = cls() 
     199        obj.parseConfig(cfg, **kw) 
     200         
     201        return obj 
     202 
     203    def parseConfig(self, cfg, prefix='', section='DEFAULT'): 
     204        '''Read config file settings 
     205        @type cfg: basestring /ConfigParser derived type 
     206        @param cfg: configuration file path or ConfigParser type object 
     207        @type prefix: basestring 
     208        @param prefix: prefix for option names e.g. "attributeQuery." 
     209        @type section: baestring 
     210        @param section: configuration file section from which to extract 
     211        parameters. 
     212        '''   
     213        if isinstance(cfg, basestring): 
     214            cfgFilePath = path.expandvars(cfg) 
     215            _cfg = CaseSensitiveConfigParser() 
     216            _cfg.read(cfgFilePath) 
     217             
     218        elif isinstance(cfg, ConfigParser): 
     219            _cfg = cfg    
     220        else: 
     221            raise AttributeError('Expecting basestring or ConfigParser type ' 
     222                                 'for "cfg" attribute; got %r type' % type(cfg)) 
     223         
     224        prefixLen = len(prefix) 
     225        for optName, val in _cfg.items(section): 
     226            if prefix: 
     227                # Filter attributes based on prefix 
     228                if optName.startswith(prefix): 
     229                    setattr(self, optName[prefixLen:], val) 
     230            else: 
     231                # No prefix set - attempt to set all attributes    
     232                setattr(self, optName, val) 
    190233         
    191234    def __getstate__(self): 
     
    208251             
    209252 
    210 class AttributeQueryResponseError(SOAPBindingInvalidResponse): 
    211     """Attribute Authority returned a SAML Response error code""" 
     253class SubjectQueryResponseError(SOAPBindingInvalidResponse): 
     254    """SAML Response error from Subject Query""" 
    212255    def __init__(self, *arg, **kw): 
    213256        SOAPBindingInvalidResponse.__init__(self, *arg, **kw) 
     
    235278 
    236279 
    237 class AttributeQuerySOAPBinding(SOAPBinding):  
    238     """SAML Attribute Query SOAP Binding 
     280class AttributeQueryResponseError(SubjectQueryResponseError): 
     281    """SAML Response error from Attribute Query""" 
     282     
     283     
     284class SubjectQuerySOAPBinding(SOAPBinding):  
     285    """SAML Subject Query SOAP Binding 
    239286     
    240287    Nb. Assumes X.509 subject type for query issuer 
     
    242289    SUBJECT_ID_OPTNAME = 'subjectID' 
    243290    ISSUER_NAME_OPTNAME = 'issuerName' 
    244     CLOCK_SKEW_OPTNAME = 'clockSkew' 
     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 
     479class 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' 
    245487     
    246488    CONFIG_FILE_OPTNAMES = ( 
     
    264506        self.__issuerName = None 
    265507        self.__queryAttributes = TypedList(Attribute) 
    266         self.__clockSkew = timedelta(seconds=0.) 
     508        self.__clockSkewTolerance = timedelta(seconds=0.) 
    267509                 
    268510        super(AttributeQuerySOAPBinding, self).__init__(**kw) 
     
    383625                            "Query to Attribute Authority") 
    384626 
    385     def _getClockSkew(self): 
    386         return self.__clockSkew 
    387  
    388     def _setClockSkew(self, value): 
     627    def _getClockSkewTolerance(self): 
     628        return self.__clockSkewTolerance 
     629 
     630    def _setClockSkewTolerance(self, value): 
    389631        if isinstance(value, (float, int, long)): 
    390             self.__clockSkew = timedelta(seconds=value) 
     632            self.__clockSkewTolerance = timedelta(seconds=value) 
    391633             
    392634        elif isinstance(value, basestring): 
    393             self.__clockSkew = timedelta(seconds=float(value)) 
     635            self.__clockSkewTolerance = timedelta(seconds=float(value)) 
    394636        else: 
    395637            raise TypeError('Expecting float, int, long or string type for ' 
    396                             '"clockSkew"; got %r' % type(value)) 
    397  
    398     clockSkew = property(fget=_getClockSkew,  
    399                          fset=_setClockSkew,  
     638                            '"clockSkewTolerance"; got %r' % type(value)) 
     639 
     640    clockSkewTolerance = property(fget=_getClockSkewTolerance,  
     641                         fset=_setClockSkewTolerance,  
    400642                         doc="Allow a clock skew in seconds for SAML Attribute" 
    401643                             " Query issueInstant parameter check")   
     
    458700            raise samlRespError 
    459701         
    460         utcNow = datetime.utcnow() + self.clockSkew 
     702        utcNow = datetime.utcnow() + self.clockSkewTolerance 
    461703        if response.issueInstant > utcNow: 
    462704            msg = ('SAML Attribute Response issueInstant [%s] is after ' 
     
    469711         
    470712        for assertion in response.assertions: 
    471             if utcNow < assertion.conditions.notBefore:             
    472                 msg = ('The current clock time [%s] is before the SAML ' 
    473                        'Attribute Response assertion conditions not before ' 
    474                        'time [%s]' %  
    475                        (SAMLDateTime.toString(utcNow), 
    476                         assertion.conditions.notBefore)) 
    477                            
    478                 samlRespError = AttributeQueryResponseError(msg) 
    479                 samlRespError.response = response 
    480                 raise samlRespError 
    481               
    482             if utcNow >= assertion.conditions.notOnOrAfter:            
    483                 msg = ('The current clock time [%s] is on or after the SAML ' 
    484                        'Attribute Response assertion conditions not on or ' 
    485                        'after time [%s]' %  
    486                        (SAMLDateTime.toString(utcNow), 
    487                         response.assertion.conditions.notOnOrAfter)) 
    488                  
    489                 samlRespError = AttributeQueryResponseError(msg)  
    490                 samlRespError.response = response 
    491                 raise samlRespError    
     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    
    492735             
    493736        return response  
Note: See TracChangeset for help on using the changeset viewer.