Changeset 5828 for TI12-security/trunk


Ignore:
Timestamp:
09/10/09 15:49:46 (10 years ago)
Author:
pjkersha
Message:

Added new regular expression pattern based matching validator class for OpenID Whitelisting.

Location:
TI12-security/trunk/python
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/openid/relyingparty/validation.py

    r5786 r5828  
    1818import os 
    1919import traceback 
     20import re 
    2021 
    2122from elementtree import ElementTree 
     23from openid.yadis.manager import Discovery 
     24 
    2225from ndg.security.common.utils.etree import QName 
    2326from ndg.security.common.utils.classfactory import instantiateClass 
    24      
     27 
    2528class _ConfigBase(object): 
    2629    """Base class for IdP Validator and Attribute Provider configuration 
     
    7982    """Parser for IdP and Attribute Provider config 
    8083    """ 
     84    VALIDATOR_ELEMNAME = "validator" 
     85    PARAMETER_ELEMNAME = "parameter" 
     86    ATTRIBUTE_PROVIDER_ELEMNAME = "attributeprovider" 
     87    NAME_ATTRNAME = "name" 
     88    VALUE_ATTRNAME = "value" 
     89     
    8190    def getValidators(self, source):   
    8291        """Retrieve IdP Validator objects from XML file 
     
    123132         
    124133        for elem in root: 
    125             if QName.getLocalPart(elem.tag).lower() == "validator":     
     134            localName = QName.getLocalPart(elem.tag).lower() 
     135            if localName == XmlConfigReader.VALIDATOR_ELEMNAME:     
    126136                validatorConfig = IdPValidatorConfig() 
    127                 validatorConfig.className = elem.attrib["name"] 
     137                 
     138                className = elem.attrib.get(XmlConfigReader.NAME_ATTRNAME) 
     139                if className is None: 
     140                    raise XmlConfigReaderError('No "%s" attribute found in ' 
     141                                               '"%s" tag' % 
     142                                              (XmlConfigReader.NAME_ATTRNAME, 
     143                                               elem.tag)) 
     144                    
     145                validatorConfig.className = className 
    128146                 
    129147                parameters = {} 
    130148                for el in elem: 
    131                     if QName.getLocalPart(el.tag).lower() == "parameter": 
    132                         if el.attrib["name"] in parameters: 
     149                    if QName.getLocalPart( 
     150                       el.tag).lower() == XmlConfigReader.PARAMETER_ELEMNAME: 
     151                         
     152                        nameAttr = el.attrib.get(XmlConfigReader.NAME_ATTRNAME) 
     153                        if nameAttr is None: 
     154                            raise XmlConfigReaderError('No "%s" attribute ' 
     155                                                       'found in "%s" tag' % 
     156                                                (XmlConfigReader.NAME_ATTRNAME, 
     157                                                 el.tag)) 
     158                        if nameAttr in parameters: 
    133159                            raise XmlConfigReaderError('Duplicate parameter ' 
    134160                                                       'name "%s" found' %  
    135                                                        el.attrib["name"]) 
     161                                                       el.attrib[ 
     162                                                XmlConfigReader.NAME_ATTRNAME]) 
    136163                             
    137                         parameters[el.attrib["name"]] = os.path.expandvars( 
    138                                                             el.attrib["value"]) 
     164                        valAttr = el.attrib.get(XmlConfigReader.VALUE_ATTRNAME) 
     165                        if valAttr is None: 
     166                            raise XmlConfigReaderError('No "%s" attribute ' 
     167                                                       'found in "%s" tag' % 
     168                                                (XmlConfigReader.VALUE_ATTRNAME, 
     169                                                 el.tag)) 
     170                                
     171                        parameters[nameAttr] = os.path.expandvars(valAttr) 
    139172             
    140173                validatorConfig.parameters = parameters 
     
    144177     
    145178    def __extractAttrProviderConfigs(self, root): 
     179        """Parse Attribute Provider configurations from the XML tree 
     180        @type root: ElementTree.Element 
     181        @param root: root element of parsed XML config file 
     182        """ 
    146183        attrProviders = [] 
    147184        validatorConfig = None 
     
    149186 
    150187        for elem in root: 
    151             if QName.getLocalPart(elem.tag).lower() == "attributeprovider": 
     188            localName = QName.getLocalPart(elem.tag).lower() 
     189            if localName == XmlConfigReader.ATTRIBUTE_PROVIDER_ELEMNAME: 
    152190                if validatorConfig is not None: 
    153191                    validatorConfig.parameters = parameters 
     
    155193                 
    156194                validatorConfig = AttributeProviderConfig() 
    157                 validatorConfig.className(elem.attrib("name")) 
    158              
    159             elif QName.getLocalPart(elem.tag).lower() == "parameter": 
    160                 if elem.attrib["name"] in parameters: 
     195                nameAttr = elem.attrib.get(XmlConfigReader.NAME_ATTRNAME) 
     196                if nameAttr is None: 
     197                    raise XmlConfigReaderError('No "%s" attribute ' 
     198                                               'found in "%s" tag' % 
     199                                               (XmlConfigReader.NAME_ATTRNAME, 
     200                                                elem.tag)) 
     201                validatorConfig.className(nameAttr) 
     202             
     203            elif localName == XmlConfigReader.PARAMETER_ELEMNAME: 
     204                 
     205                nameAttr = elem.attrib.get(XmlConfigReader.NAME_ATTRNAME) 
     206                if nameAttr is None: 
     207                    raise XmlConfigReaderError('No "%s" attribute ' 
     208                                               'found in "%s" tag' % 
     209                                               (XmlConfigReader.NAME_ATTRNAME, 
     210                                                elem.tag)) 
     211                        
     212                if nameAttr in parameters: 
    161213                    raise XmlConfigReaderError('Duplicate parameter name "%s" ' 
    162                                                'found' % elem.attrib["name"]) 
    163              
    164                 parameters[elem.attrib["name"]] = elem.attrib["value"] 
     214                                               'found' % nameAttr) 
     215                     
     216                valAttr = elem.attrib.get(XmlConfigReader.VALUE_ATTRNAME) 
     217                if valAttr is None: 
     218                    raise XmlConfigReaderError('No "%s" attribute ' 
     219                                               'found in "%s" tag' % 
     220                                               (XmlConfigReader.VALUE_ATTRNAME, 
     221                                                elem.tag)) 
     222             
     223                parameters[nameAttr] = elem.attrib[valAttr] 
    165224             
    166225        if validatorConfig != None: 
     
    225284 
    226285class SSLClientAuthNValidator(SSLIdPValidator): 
    227     parameters = { 
     286    PARAMETERS = { 
    228287        'configFilePath': basestring, 
    229288        'caCertDirPath': basestring, 
     
    235294    def __init__(self): 
    236295        """Set-up default SSL context for HTTPS requests""" 
    237         for p in SSLClientAuthNValidator.parameters: 
     296        for p in SSLClientAuthNValidator.PARAMETERS: 
    238297            setattr(self, p, None) 
    239298             
     
    241300        '''@raise ConfigException:'''  
    242301        for name, val in parameters.items(): 
    243             if name not in SSLClientAuthNValidator.parameters: 
     302            if name not in SSLClientAuthNValidator.PARAMETERS: 
    244303                raise AttributeError('Invalid parameter name "%s".  Valid ' 
    245304                                     'names are %r' % (name, 
    246                                     SSLClientAuthNValidator.parameters.keys())) 
    247                  
    248             if not isinstance(val, SSLClientAuthNValidator.parameters[name]): 
     305                                    SSLClientAuthNValidator.PARAMETERS.keys())) 
     306                 
     307            if not isinstance(val, SSLClientAuthNValidator.PARAMETERS[name]): 
    249308                raise TypeError('Invalid type %r for parameter "%s" expecting ' 
    250309                                '%r ' %  
    251                                 (val.__class__,  
     310                                (type(val),  
    252311                                 name,  
    253                                  SSLClientAuthNValidator.parameters[name])) 
     312                                 SSLClientAuthNValidator.PARAMETERS[name])) 
    254313         
    255314            setattr(self, name, os.path.expandvars(val)) 
     
    298357 
    299358 
     359class FileBasedIdentityUriValidator(IdPValidator): 
     360    """Validate OpenID identity URI against a list of regular expressions 
     361    which specify the allowable identities.  The list is read from a simple 
     362    flat file - one pattern per line 
     363    """     
     364    PARAMETERS = { 
     365        'configFilePath': basestring, 
     366    } 
     367    CONFIGFILE_COMMENT_CHAR = '#' 
     368     
     369    def __init__(self): 
     370        self.__configFilePath = None 
     371        self.__identityUriPatterns = None 
     372 
     373    def _setIdentityUriPatterns(self, value): 
     374        if not isinstance(value, dict): 
     375            raise TypeError('Expecting a dict of pattern objects keyed by ' 
     376                            'pattern string for "identityUriPatterns" object; ' 
     377                            'got %r' % type(value)) 
     378        self.__identityUriPatterns = value 
     379 
     380    identityUriPatterns = property(fget=lambda self:self.__identityUriPatterns,  
     381                                   fset=_setIdentityUriPatterns,  
     382                                   doc="list of regular expression objects " 
     383                                       "to match input identity URIs against") 
     384         
     385    def _getConfigFilePath(self): 
     386        return self.__configFilePath 
     387 
     388    def _setConfigFilePath(self, value): 
     389        if not isinstance(value, basestring): 
     390            raise TypeError('Expecting string type for "configFilePath"; got ' 
     391                            '%r' % type(value)) 
     392        self.__configFilePath = value 
     393 
     394    configFilePath = property(fget=_getConfigFilePath,  
     395                              fset=_setConfigFilePath,  
     396                              doc="Configuration file path for this validator") 
     397 
     398    def initialize(self, **parameters): 
     399        '''@raise ConfigException:'''  
     400        for name, val in parameters.items(): 
     401            if name not in FileBasedIdentityUriValidator.PARAMETERS: 
     402                raise AttributeError('Invalid parameter name "%s".  Valid ' 
     403                            'names are %r' % (name, 
     404                            FileBasedIdentityUriValidator.PARAMETERS.keys())) 
     405                 
     406            if not isinstance(val,  
     407                              FileBasedIdentityUriValidator.PARAMETERS[name]): 
     408                raise TypeError('Invalid type %r for parameter "%s" expecting ' 
     409                            '%r ' %  
     410                            (type(val),  
     411                             name,  
     412                             FileBasedIdentityUriValidator.PARAMETERS[name])) 
     413         
     414            setattr(self, name, os.path.expandvars(val)) 
     415 
     416        self._parseConfigFile() 
     417         
     418    def _parseConfigFile(self): 
     419        """Read the configFile containing identity URI regular expressions 
     420        """ 
     421        try: 
     422            configFile = open(self.configFilePath) 
     423        except IOError, e: 
     424            raise ConfigException('Error parsing %r configuration file "%s": ' 
     425                                  '%s' % (self.__class__.__name__, 
     426                                   e.filename, e.strerror)) 
     427         
     428        lines = re.split('\s', configFile.read().strip()) 
     429        self.identityUriPatterns = dict([ 
     430            (pat, re.compile(pat)) 
     431            for pat in lines if not pat.startswith( 
     432                        FileBasedIdentityUriValidator.CONFIGFILE_COMMENT_CHAR) 
     433        ]) 
     434        
     435    def validate(self, idpEndpoint, idpIdentity): 
     436        '''Match user identity URI against list of acceptable patterns parsed 
     437        from config file.  The idpEndpoint is ignored 
     438         
     439        @type idpEndpoint: basestring 
     440        @param idpEndpoint: endpoint for OpenID Provider service being  
     441        discovered 
     442        @type idpIdentity: basestring 
     443        @param idpIdentity: endpoint for OpenID Provider service being  
     444        @raise IdPInvalidException: 
     445        '''  
     446        for patStr, pat in self.identityUriPatterns.items(): 
     447            if pat.match(idpIdentity) is not None: 
     448                log.debug("Identity URI %r matches whitelist pattern %r" % 
     449                          (idpIdentity, patStr)) 
     450                break # identity matches: return silently 
     451        else: 
     452            raise IdPInvalidException("OpenID identity URI %r doesn't match " 
     453                                      "the whitelisted patterns" % 
     454                                      idpIdentity) 
     455     
     456     
    300457class IdPValidationDriver(object): 
    301458    """Parse an XML Validation configuration containing XML Validators and  
    302459    execute these against the Provider (IdP) input"""    
    303     idPValidatorBaseClass = IdPValidator 
     460    IDP_VALIDATOR_BASE_CLASS = IdPValidator 
     461    IDP_CONFIG_FILEPATH_ENV_VARNAME = "IDP_CONFIG_FILE" 
    304462     
    305463    def __init__(self): 
    306         self._idPValidators = [] 
     464        self.__idPValidators = [] 
    307465         
    308466    def _get_idPValidators(self): 
    309         return self._idPValidators 
     467        return self.__idPValidators 
    310468     
    311469    def _set_idPValidators(self, idPValidators): 
    312470        badValidators = [i for i in idPValidators  
    313471                         if not isinstance(i,  
    314                                         self.__class__.idPValidatorBaseClass)] 
     472                                    self.__class__.IDP_VALIDATOR_BASE_CLASS)] 
    315473        if len(badValidators): 
    316474            raise TypeError("Input validators must be of IdPValidator derived " 
    317475                            "type") 
    318476                 
    319         self._idPValidators = idPValidators 
     477        self.__idPValidators = idPValidators 
    320478         
    321479    idPValidators = property(fget=_get_idPValidators, 
     
    323481                             doc="list of IdP Validators") 
    324482 
    325     def readConfig(self, idpConfigFilePath=None): 
     483    @classmethod 
     484    def readConfig(cls, idpConfigFilePath=None): 
     485        """Read IdP Validation configuration file.  This is an XML document 
     486        containing a list of validator class names and their initialisation 
     487        parameters 
     488        """ 
    326489        validators = [] 
    327490         
    328491        if idpConfigFilePath is None: 
    329             idpConfigFilePath = os.environ.get("IDP_CONFIG_FILE") 
     492            idpConfigFilePath = os.environ.get( 
     493                        IdPValidationDriver.IDP_CONFIG_FILEPATH_ENV_VARNAME) 
    330494             
    331495        if idpConfigFilePath is None: 
     
    347511            except Exception, e:   
    348512                log.error("Failed to initialise validator %s: %s",  
    349                           validatorConfig.className, 
    350                           e) 
     513                          validatorConfig.className, traceback.format_exc()) 
    351514                 
    352515        return validators 
    353516     
    354     def performIdPValidation(self, identifier, discoveries): 
     517    def performIdPValidation(self,  
     518                             identifier,  
     519                             discoveries=(Discovery(None, None),)): 
    355520        """Perform all IdPValidation for all configured IdPValidators.  if 
    356521        the setIdPValidator method was used to initialise a list of 
     
    359524        IdPValidators found are appended to the initial list and run in 
    360525        addition. 
     526         
     527        @param identifier: OpenID identity URL 
     528        @type identifier: basestring 
     529        @param discoveries: list of discovery instances.  Default to a single 
     530        one with a provider URI of None 
     531        @type discoveries: openid.yadis.discover.Discover 
    361532        """ 
    362533        validators = self.readConfig() 
     
    373544            for validator in validators:    
    374545                for discoveryInfo in discoveries: 
    375                     try:                     
    376                         validator.validate(discoveryInfo.getOPEndpoint(),  
    377                                            identifier.getIdentifier()) 
     546                    try: 
     547                        validator.validate(discoveryInfo.url, identifier) 
    378548 
    379549                        log.info("Whitelist Validator %r accepting endpoint: " 
    380                                  "%s", validator, 
    381                                  discoveryInfo.getOPEndpoint()) 
     550                                 "%s", validator, discoveryInfo.url) 
    382551 
    383552                        newDiscoveries.append(discoveryInfo) 
    384553                     
     554                    except IdPInvalidException, e: 
     555                        log.warning("Whitelist Validator %r rejecting " 
     556                                    "identifier: %s: %s", validator,  
     557                                    identifier, e) 
     558                                                
    385559                    except Exception, e:         
    386                         log.warning("Whitelist Validator %r rejecting " 
    387                                     "endpoint: %s: %s", validator,  
    388                                     discoveryInfo.getOPEndpoint(), e) 
     560                        log.warning("Error with Whitelist Validator %r " 
     561                                    "rejecting identity: %s: %s", validator,  
     562                                    identifier, traceback.format_exc()) 
    389563                         
    390564            if len(newDiscoveries) > 0: 
     
    404578    '''Validate an IdP using the certificate it returns from an SSL based 
    405579    request''' 
    406     idPValidatorBaseClass = SSLIdPValidator 
     580    IDP_VALIDATOR_BASE_CLASS = SSLIdPValidator 
    407581     
    408582    def __init__(self, idpConfigFilePath=None): 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/unit/openid/relyingparty/validation/idpvalidator.xml

    r5779 r5828  
    1717        <parameter name="config-file" value="$NDGSEC_UNITTEST_IDPVALIDATION_DIR/providerIdentifierWhitelist.cfg"/> 
    1818    </validator> 
     19    <validator  
     20        name="ndg.security.server.wsgi.openid.relyingparty.validation.FileBasedIdentityUriValidator"> 
     21        <parameter  
     22         name="configFilePath"  
     23         value="$NDGSEC_UNITTEST_IDPVALIDATION_DIR/identityPatternWhitelist.cfg" 
     24        /> 
     25     
     26    </validator> 
    1927</IdPValidators> 
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/unit/openid/relyingparty/validation/test_validation.py

    r5779 r5828  
    1818    IdPValidator, IdPValidationDriver, IdPInvalidException, \ 
    1919    SSLIdPValidationDriver, SSLClientAuthNValidator 
     20     
    2021     
    2122class ProviderWhitelistValidator(IdPValidator): 
     
    6768class IdPValidationTestCase(BaseTestCase): 
    6869    thisDir = os.path.dirname(os.path.abspath(__file__)) 
    69     idpConfigFilePath = os.path.join(thisDir, 'idpvalidator.xml') 
     70    IDP_CONFIG_FILEPATH = os.path.join(thisDir, 'idpvalidator.xml') 
    7071    os.environ['NDGSEC_UNITTEST_IDPVALIDATION_DIR'] = thisDir 
    7172     
     
    8283         
    8384    def test02WithIdPConfigFile(self): 
    84         identifier = IdentifierPlaceHolder() 
    85         discoveries = [DiscoveryInfoPlaceHolder()] 
     85        identifier = 'https://pjk.badc.rl.ac.uk' 
    8686         
    87         os.environ['IDP_CONFIG_FILE'] = IdPValidationTestCase.idpConfigFilePath 
     87        os.environ[IdPValidationDriver.IDP_CONFIG_FILEPATH_ENV_VARNAME 
     88            ] = IdPValidationTestCase.IDP_CONFIG_FILEPATH 
     89             
    8890        idPValidationDriver = IdPValidationDriver() 
    89         validDiscoveries = idPValidationDriver.performIdPValidation(identifier, 
    90                                                                 discoveries) 
    91         self.assert_(len(validDiscoveries) == 1) 
     91        validDiscoveries = idPValidationDriver.performIdPValidation(identifier) 
     92        self.assert_(len(validDiscoveries) == 2) 
    9293         
    9394    def test03SSLValidation(self): 
     
    101102        idPValidationDriver(1, X509StoreCtxPlaceHolder()) 
    102103         
     104         
    103105if __name__ == "__main__": 
    104106    unittest.main()         
Note: See TracChangeset for help on using the changeset viewer.