Ignore:
Timestamp:
24/06/10 14:30:12 (11 years ago)
Author:
pjkersha
Message:

Incomplete - task 2: XACML-Security Integration

  • Major cleanup of function factories for efficiency. Only the required factories and function classes are loaded and any loaded classes are cached for future calls. All unit tests pass.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/NDG_XACML/ndg/xacml/core/functions/__init__.py

    r7064 r7072  
    282282 
    283283class FunctionClassFactoryBase(FunctionClassFactoryInterface): 
    284     """Base implementation for XACML Function Class Factory.  Derived types  
    285     should be implemented in sub-modules of ndg.xacml.core.functions  
     284    """Base implementation for XACML Function Class Factory.  There should be 
     285    one derived type for each function family implemented in sub-modules of  
     286    ndg.xacml.core.functions  
    286287     
    287288    e.g. 
    288289     
    289     for urn:oasis:names:tc:xacml:1.0:function:string-at-least-one-member-of a  
     290    for urn:oasis:names:tc:xacml:1.0:function:<type>-at-least-one-member-of a  
    290291    class factory should exist, 
    291292     
    292293    ndg.xacml.core.functions.v1.at_least_one_member_of.FunctionClassFactory 
    293294     
    294     which will be capable of returning an AbstractFunction derived type: 
    295      
    296     StringAtLeastOneMemberOf     
     295    which will be capable of returning a type derived from AbstractFunction: 
     296     
     297    <type>AtLeastOneMemberOf     
     298     
     299    e.g. StringAtLeastOneMemberOf, BooleanAtLeastOneMemberOf. 
    297300     
    298301    This class is for convenience only some function factories are better  
     
    318321    ndg.xacml.core.functions.v1.at_least_one_member_of.AtLeastOneMemberOfBase 
    319322    @type FUNCTION_BASE_CLASS: NoneType (but AbstractFunction derived type in  
    320     derived function facotry class) 
     323    derived function factory class) 
    321324    """ 
    322325     
     
    327330    URN_SEP = ':' 
    328331    FUNCTION_NAME_SEP = '-' 
    329  
     332    __slots__ = ('__map', 'attributeValueClassFactory', 'functionSuffix') 
     333     
    330334    def __init__(self): 
    331         """Create classes for each <type>-at-least-one-member-of function and 
    332         place in a look-up table 
    333         """ 
     335        '''This class is in fact abstract - derived types must define the  
     336        FUNCTION_NS_SUFFIX and FUNCTION_BASE_CLASS class variables 
     337        ''' 
    334338        if None in (self.__class__.FUNCTION_NS_SUFFIX,  
    335339                    self.__class__.FUNCTION_BASE_CLASS): 
     
    343347                            'iterable of string type function identifiers; got ' 
    344348                            '%r' % self.__class__.FUNCTION_NAMES) 
    345              
    346         self.__map = {} 
     349 
     350        self.__map = {}    
     351         
     352        # Enables creation of matching attribute types to relevant to the  
     353        # function classes     
     354        self.attributeValueClassFactory = AttributeValueClassFactory() 
     355             
     356         
    347357        functionSuffixParts = self.__class__.FUNCTION_NS_SUFFIX.split( 
    348358                                            self.__class__.FUNCTION_NAME_SEP) 
    349         functionSuffix = ''.join([n[0].upper() + n[1:]  
     359        self.functionSuffix = ''.join([n[0].upper() + n[1:]  
    350360                                  for n in functionSuffixParts if n]) 
    351361         
    352         attributeValueClassFactory = AttributeValueClassFactory() 
    353          
    354         for identifier in self.__class__.FUNCTION_NAMES:             
    355             # Extract the function name and the type portion of the function 
    356             # name in order to make an implementation of a class to handle it 
    357             functionName = identifier.split(self.__class__.URN_SEP)[-1] 
    358             if functionName == 'xpath-node-match': 
    359                 pass 
    360             typePart = functionName.split(self.__class__.FUNCTION_NS_SUFFIX)[0] 
    361              
    362             # Attempt to infer from the function name the associated type 
    363             typeName = typePart[0].upper() + typePart[1:] 
    364              
    365             # Remove any hyphens converting to camel case 
    366             if '-' in typeName: 
    367                 typeName = ''.join([i[0].upper() + i[1:] 
    368                                     for i in typeName.split('-')]) 
    369                  
    370             typeURI = AttributeValue.TYPE_URI_MAP.get(typeName) 
    371             if typeURI is None: 
    372                 # Ugly hack to allow for XPath node functions 
    373                 if typePart == 'xpath-node': 
    374                     typeURI = AttributeValue.TYPE_URI_MAP['String'] 
    375                 else: 
    376                     raise TypeError('No AttributeValue.TYPE_URI_MAP entry for ' 
    377                                     '%r type' % typePart)  
    378                  
    379             _type = attributeValueClassFactory(typeURI) 
    380             if _type is None: 
    381                 raise TypeError('No AttributeValue.TYPE_MAP entry for %r type' % 
    382                                 typeName) 
    383                
    384             className = typeName + functionSuffix 
    385             classVars = { 
    386                 'TYPE': _type, 
    387                 'FUNCTION_NS': identifier 
    388             } 
    389              
    390             functionClass = type(className,  
    391                                  (self.__class__.FUNCTION_BASE_CLASS, ),  
    392                                  classVars) 
    393              
    394             self.__map[identifier] = functionClass 
     362    def initAllFunctionClasses(self): 
     363        """Create classes for all functions for a data type e.g. a derived class 
     364        could implement a factory for <type>-at-least-one-member-of functions: 
     365        string-at-least-one-member-of, boolean-at-least-one-member-of, etc.  
     366         
     367        Function classes are placed in a look-up table __map for the __call__() 
     368        method to access 
     369         
     370        In practice, there shouldn't be a need to load all the functions in 
     371        one go.  The __call__ method loads functions and caches them as needed. 
     372        """         
     373        for identifier in self.__class__.FUNCTION_NAMES: 
     374            self.loadFunction(identifier)         
     375 
     376    def loadFunction(self, identifier): 
     377        """Create a class for the given function namespace and cache it in the  
     378        function class look-up table for future requests.  Note that this call 
     379        overwrites any existing entry in the cache whereas __call__ will try 
     380        to use an entry in the cache if it already exists 
     381         
     382        @param identifier: XACML function namespace 
     383        @type identifier: basestring 
     384        """ 
     385 
     386        # str.capitalize doesn't do what's required: need to capitalize the  
     387        # first letter of the word BUT retain camel case for the rest of it 
     388        _capitalize = lambda s: s[0].upper() + s[1:] 
     389         
     390        # Extract the function name and the type portion of the function 
     391        # name in order to make an implementation of a class to handle it 
     392        functionName = identifier.split(self.__class__.URN_SEP)[-1] 
     393        typePart = functionName.split(self.__class__.FUNCTION_NS_SUFFIX)[0] 
     394         
     395        # Attempt to infer from the function name the associated type 
     396        typeName = _capitalize(typePart) 
     397         
     398        # Remove any hyphens converting to camel case 
     399        if '-' in typeName: 
     400            typeName = ''.join([_capitalize(i) for i in typeName.split('-')]) 
     401             
     402        typeURI = AttributeValue.TYPE_URI_MAP.get(typeName) 
     403        if typeURI is None: 
     404            # Ugly hack to allow for XPath node functions 
     405            if typePart == 'xpath-node': 
     406                typeURI = AttributeValue.TYPE_URI_MAP['String'] 
     407            else: 
     408                raise TypeError('No AttributeValue.TYPE_URI_MAP entry for ' 
     409                                '%r type' % typePart)  
     410             
     411        _type = self.attributeValueClassFactory(typeURI) 
     412        if _type is None: 
     413            raise TypeError('No AttributeValue.TYPE_MAP entry for %r type' % 
     414                            typeName) 
     415           
     416        className = typeName + self.functionSuffix 
     417        classVars = { 
     418            'TYPE': _type, 
     419            'FUNCTION_NS': identifier 
     420        } 
     421         
     422        functionClass = type(className,  
     423                             (self.__class__.FUNCTION_BASE_CLASS, ),  
     424                             classVars) 
     425         
     426        self.__map[identifier] = functionClass 
    395427             
    396428    def __call__(self, identifier): 
     
    405437        found 
    406438        """ 
     439        # Check the cache first 
     440        functionClass = self.__map.get(identifier) 
     441        if functionClass is None: 
     442            # No class set in the cache - try loading the new class and updating 
     443            # the cache. 
     444            self.loadFunction(identifier) 
     445             
     446        # This should result in a safe retrieval from the cache because of the 
     447        # above check - None return would result otherwise. 
    407448        return self.__map.get(identifier) 
    408449         
     
    417458         
    418459class FunctionMap(VettedDict): 
    419     """Map function IDs to their implementations""" 
     460    """Map function IDs to their class implementations in the various function 
     461    sub-modules.  It provide a layer over the various  
     462    FunctionClassFactoryInterface implementations so that a function class can  
     463    be obtained directly from a given XACML function URN.   
     464    """ 
    420465    FUNCTION_PKG_PREFIX = 'ndg.xacml.core.functions.' 
    421466     
     
    433478     
    434479    def __init__(self): 
    435         """Force function entries to derive from AbstractFunction and IDs to 
    436         be string type 
     480        """Force type for dictionary key value pairs: function values must be 
     481        of AbstractFunction derived type and ID keys string type 
    437482        """         
    438483        # Filters are defined as staticmethods but reference via self here to  
    439484        # enable derived class to override them as standard methods without 
    440485        # needing to redefine this __init__ method             
    441         super(FunctionMap, self).__init__(self.keyFilter, self.valueFilter) 
     486        VettedDict.__init__(self, self.keyFilter, self.valueFilter) 
     487         
     488        # This classes maintains a list of XACML function URN -> Function class 
     489        # mappings.  This additional dict enables caching of class factories  
     490        # used to obtain the function classes.  There is one class factory per 
     491        # function module e.g. ndg.xacml.core.functions.v1.equal contains a  
     492        # class factory which creates the various  
     493        # urn:oasis:names:tc:xacml:1.0:function:<type>-equal function classes 
     494        self.__classFactoryMap = {} 
    442495         
    443496    @staticmethod 
     
    462515        return True  
    463516            
    464     def load(self): 
     517    def loadAll(self): 
    465518        """Load function map with implementations from the relevant function 
    466519        package""" 
    467520         
    468521        for functionNs in XacmlFunctionNames.FUNCTION_NAMES: 
    469             self._loadFunction(functionNs) 
    470              
    471     @classmethod 
    472     def withLoadedMap(cls): 
    473         """Return a pre-loaded map""" 
    474         functionMap = cls() 
    475         functionMap.load() 
    476         return functionMap 
    477              
    478     def _loadFunction(self, functionNs): 
    479         """Get package to retrieve function class from for given namespace 
     522            self.loadFunction(functionNs) 
     523             
     524    def loadFunction(self, functionNs): 
     525        """Get package to retrieve function class for the given XACML function 
     526        namespace 
     527         
     528        @param functionNs: XACML function namespace 
     529        @type functionNs: basestring 
    480530        """ 
     531        functionFactory = self.__classFactoryMap.get(functionNs) 
     532        if functionFactory is not None: 
     533            # Get function class from previously cached factory 
     534            self[functionNs] = functionFactory(functionNs) 
     535            return 
     536             
     537        # No Factory has been cached for this function yet 
    481538        cls = FunctionMap 
    482539        classPath = None 
     
    484541        for namespacePrefix, pkgNamePrefix in cls.SUPPORTED_NSS.items(): 
    485542            if functionNs.startswith(namespacePrefix): 
    486                 # Namespace is recognised - translate into a path to a function 
    487                 # class in the right functions package 
     543                # Namespace is recognised - translate into a path to a  
     544                # function class in the right functions package 
    488545                functionName = functionNs.split(namespacePrefix)[-1] 
    489546                functionNameParts = functionName.split('-') 
     
    506563                                         'recognised: %r' % functionNs)  
    507564                        
    508         # Try instantiating the function class and loading it into the  
    509         # map 
     565        # Try instantiating the function class and loading it into the map 
    510566        try: 
    511567            functionFactory = callModuleObject(classPath) 
    512568                       
    513         except (ImportError, AttributeError): 
     569        except (ImportError, AttributeError), e: 
    514570            log.error("Error importing function factory class %r for function " 
    515                       "identifier %r: %s", classPath, functionNs,  
    516                       traceback.format_exc()) 
     571                      "identifier %r: %s", classPath, functionNs, str(e)) 
    517572             
    518573            # No implementation exists - default to Abstract function 
     
    520575        else: 
    521576            self[functionNs] = functionFactory(functionNs) 
    522              
    523          
    524              
    525  
    526  
    527          
     577            self.__classFactoryMap[functionNs] = functionFactory 
     578                        
     579    def __getitem__(self, key): 
     580        """Override base class implementation to load and cache function classes 
     581        if they don't otherwise exist 
     582        """ 
     583        functionClass = VettedDict.get(self, key) 
     584        if functionClass is None: 
     585            self.loadFunction(key) 
     586             
     587        return VettedDict.__getitem__(self, key) 
     588         
     589    def get(self, key, *arg): 
     590        """Likewise to __getitem__, enable loading and caching of function  
     591        classes if they don't otherwise exist 
     592        """ 
     593        functionClass = VettedDict.get(self, key, *arg) 
     594        if functionClass is None: 
     595            self.loadFunction(key) 
     596            return VettedDict.get(self, key, *arg)     
     597        else: 
     598            return functionClass 
     599 
     600 
     601         
Note: See TracChangeset for help on using the changeset viewer.