Changeset 7314 for TI12-security


Ignore:
Timestamp:
11/08/10 16:39:41 (9 years ago)
Author:
pjkersha
Message:

Incomplete - task 2: XACML-Security Integration

  • significant progress on PIP - can init from config file and added unit tests
Location:
TI12-security/trunk/NDGSecurity/python
Files:
5 added
2 edited

Legend:

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

    r7076 r7314  
    99__contact__ = "Philip.Kershaw@stfc.ac.uk" 
    1010__revision__ = '$Id$' 
     11import UserDict 
    1112 
    1213# Interpret a string as a boolean 
    1314str2Bool = lambda str: str.lower() in ("yes", "true", "t", "1") 
     15 
    1416 
    1517class UniqList(list): 
     
    104106         
    105107        dict.update(self, d, **kw) 
     108         
     109   
     110class VettedDict(UserDict.DictMixin): 
     111    """Enforce custom checking on keys and items before addition to a  
     112    dictionary 
     113    """ 
     114     
     115    def __init__(self, *args): 
     116        """Initialise setting the allowed type or types for keys and items 
     117         
     118        @param args: two arguments: the first is a callable which filters for  
     119        permissable keys in this dict, the second sets the type or list of 
     120        types permissable for items in this dict 
     121        @type args: tuple 
     122        """ 
     123        if len(args) != 2: 
     124            raise TypeError('__init__() takes 2 arguments, KeyFilter and ' 
     125                            'valueFilter (%d given)' % len(args)) 
     126         
     127        # Validation of inputs 
     128        for arg, argName in zip(args, ('KeyFilter', 'valueFilter')): 
     129            if not callable(arg): 
     130                raise TypeError('Expecting callable for %r input; got %r' %  
     131                                (argName, type(arg))) 
     132 
     133        self.__KeyFilter, self.__valueFilter = args 
     134         
     135        self.__map = {} 
     136         
     137    def _verifyKeyValPair(self, key, val): 
     138        """Check given key value pair and return False if they should be  
     139        filtered out.  Filter functions may also raise an exception if they 
     140        wish to abort completely 
     141         
     142        @param key: dict key to check 
     143        @type key: basestring 
     144        @param val: value to check 
     145        @type val: any 
     146        """ 
     147        if not self.__KeyFilter(key): 
     148            return False 
     149         
     150        elif not self.__valueFilter(val): 
     151            return False 
     152         
     153        else: 
     154            return True 
     155                   
     156    def __setitem__(self, key, val): 
     157        """Enforce type checking when setting new items 
     158         
     159        @param key: key for item to set 
     160        @type key: any 
     161        @param val: value to set for this key 
     162        @type val: any 
     163        """        
     164        if self._verifyKeyValPair(key, val): 
     165            self.__map[key] = val 
     166 
     167    def __getitem__(self, key): 
     168        """Provide implementation for getting items 
     169        @param key: key for item to retrieve 
     170        @type key: any 
     171        @return: value for input key 
     172        @rtype: any 
     173        """ 
     174        if key not in self.__map: 
     175            raise KeyError('%r key not found in dict' % key) 
     176         
     177        return self.__map[key] 
     178     
     179    def get(self, key, *arg): 
     180        """Provide implementation of get item with default 
     181         
     182        @param key: key for item to retrieve 
     183        @type key: any 
     184        @param arg: use to set a default argument 
     185        @type arg: tuple 
     186        """ 
     187        if key in self.__map: 
     188            return self.__map[key] 
     189         
     190        elif len(arg) > 1: 
     191            # Default value set 
     192            return arg[1] 
     193        else: 
     194            return None 
     195 
     196    def __repr__(self): 
     197        return repr(self.__map) 
     198     
     199    def keys(self): 
     200        return self.__map.keys() 
     201     
     202    def items(self): 
     203        return self.__map.items() 
     204     
     205    def values(self): 
     206        return self.__map.values() 
     207     
     208    def __contains__(self, val): 
     209        return self.__map.__contains__(val) 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/pip/saml_pip.py

    r7310 r7314  
    1313 
    1414from os import path 
    15 from ConfigParser import SafeConfigParser 
     15from ConfigParser import SafeConfigParser, ConfigParser 
    1616 
    1717from ndg.xacml.core.context.pipinterface import PIPInterface 
     
    2020from ndg.saml.saml2.core import (AttributeQuery as SamlAttributeQuery,  
    2121                                 Attribute as SamlAttribute) 
    22 from ndg.saml.utils import TypedList as SamlTypedList 
     22from ndg.saml.utils import TypedList 
    2323from ndg.saml.saml2.binding.soap.client.attributequery import \ 
    2424                                            AttributeQuerySslSOAPBinding 
    2525                                             
    26 from ndg.security.common.X509 import X509Cert 
     26from ndg.security.common.utils import VettedDict 
    2727 
    2828 
    2929class PIP(PIPInterface): 
    3030    '''Policy Information Point enables XACML PDP to query for additional user 
    31     attrbiutes.  The PDP does this indirectly via the Context Handler 
     31    attributes.  The PDP does this indirectly via the Context Handler    
    3232    ''' 
    3333    # Subject attributes makes no sense for external configuration - these  
     
    4545    ATTRIBUTE_QUERY_ATTRNAME_OFFSET = LEN_ATTRIBUTE_QUERY_ATTRNAME + 1 
    4646     
    47     __slots__ = ('__mappingFilePath',) 
     47    DEFAULT_OPT_PREFIX = 'saml_pip.' 
     48 
     49    __slots__ = ( 
     50        '__subjectAttributeId', 
     51        '__mappingFilePath',  
     52        '__attribute2AttributeAuthorityMap', 
     53        '__attributeQueryBinding' 
     54    ) 
    4855     
    4956    def __init__(self): 
    5057        '''Initialise settings for connection to an Attribute Authority''' 
    51         self.__attributeQueryBinding = AttributeQuerySslSOAPBinding()     
     58        self.__subjectAttributeId = None 
    5259        self.__mappingFilePath = None 
    53  
     60         
     61        # Force mapping dict to have string type keys and items 
     62        _typeCheckers = (lambda val: isinstance(val, basestring),)*2 
     63        self.__attribute2AttributeAuthorityMap = VettedDict(*_typeCheckers) 
     64         
     65        self.__attributeQueryBinding = AttributeQuerySslSOAPBinding() 
     66         
     67    def _get_subjectAttributeId(self): 
     68        return self.__subjectAttributeId 
     69 
     70    def _set_subjectAttributeId(self, value): 
     71        if not isinstance(value, basestring): 
     72            raise TypeError('Expecting string type for "subjectAttributeId"; ' 
     73                            'got %r' % type(value)) 
     74        self.__subjectAttributeId = value 
     75 
     76    subjectAttributeId = property(_get_subjectAttributeId,  
     77                                  _set_subjectAttributeId, 
     78                                  doc="The attribute ID of the subject value " 
     79                                      "to extract from the XACML request " 
     80                                      "context and pass in the SAML attribute " 
     81                                      "query") 
     82                                        
    5483    def _getMappingFilePath(self): 
    5584        return self.__mappingFilePath 
     
    5988            raise TypeError('Expecting string type for "mappingFilePath"; got ' 
    6089                            '%r' % type(value)) 
    61         self.__mappingFilePath = value 
     90        self.__mappingFilePath = path.expandvars(value) 
    6291 
    6392    mappingFilePath = property(_getMappingFilePath,  
    6493                               _setMappingFilePath,  
    65                                doc="Mapping File maps attribute IDs to the " 
    66                                    "Attribute Authorities from which they may " 
    67                                    "be queried for") 
    68  
     94                               doc="Mapping File maps Attribute ID -> " 
     95"Attribute Authority mapping file.  The PIP, on receipt of a query from the " 
     96"XACML context handler, checks the attribute(s) being queried for and looks up " 
     97"this mapping to determine which attribute authority to query to find out if " 
     98"the subject has the attribute in their entitlement.") 
     99     
     100    attribute2AttributeAuthorityMap = property( 
     101                    fget=lambda self: self.__attribute2AttributeAuthorityMap, 
     102                    doc="Mapping from attribute Id to attribute authority " 
     103                        "endpoint") 
     104     
     105    @property 
     106    def attributeQueryBinding(self): 
     107        """SAML SOAP Attribute Query client binding object""" 
     108        return self.__attributeQueryBinding 
     109     
    69110    @classmethod 
    70111    def fromConfig(cls, cfg, **kw): 
     
    96137            setattr(self, optName, val) 
    97138 
    98     def parseConfig(self, cfg, prefix='', section='DEFAULT'): 
     139    def parseConfig(self, cfg, prefix=DEFAULT_OPT_PREFIX, section='DEFAULT'): 
    99140        '''Read config settings from a file, config parser object or dict 
    100141         
     
    103144        @type prefix: basestring 
    104145        @param prefix: prefix for option names e.g. "attributeQuery." 
    105         @type section: baestring 
     146        @type section: basetring 
    106147        @param section: configuration file section from which to extract 
    107148        parameters. 
     
    109150        if isinstance(cfg, basestring): 
    110151            cfgFilePath = path.expandvars(cfg) 
    111             _cfg = SafeConfigParser() 
     152             
     153            # Add a 'here' helper option for setting dir paths in the config 
     154            # file 
     155            hereDir = path.abspath(path.dirname(cfgFilePath)) 
     156            _cfg = SafeConfigParser(defaults={'here': hereDir}) 
     157             
     158            # Make option name reading case sensitive 
     159            _cfg.optionxform = str 
    112160            _cfg.read(cfgFilePath) 
    113161            items = _cfg.items(section) 
     
    129177                # Filter attributes based on prefix 
    130178                if optName.startswith(prefix): 
    131                     self.__setAttr(optName[prefixLen:], val) 
     179                    setattr(self, optName[prefixLen:], val) 
    132180            else: 
    133181                # No prefix set - attempt to set all attributes    
    134                 self.__setAttr(optName, val) 
     182                setattr(self, optName, val) 
    135183                             
    136 #    def __setattr__(self, name, value): 
    137 #        """Enable setting of AttributeQuerySslSOAPBinding attributes from 
    138 #        names starting with attributeQuery.* / attributeQuery_*.  Addition for 
    139 #        setting these values from ini file 
    140 #        """ 
    141 # 
    142 #        # Coerce into setting AttributeQuerySslSOAPBinding attributes -  
    143 #        # names must start with 'attributeQuery\W' e.g. 
    144 #        # attributeQuery.clockSkew or attributeQuery_issuerDN 
    145 #        if name.startswith(self.__class__.ATTRIBUTE_QUERY_ATTRNAME): 
    146 #            queryAttrName = name[ 
    147 #                                self.__class__.ATTRIBUTE_QUERY_ATTRNAME_OFFSET:] 
    148 #             
    149 #            # Skip subject and attribute Id related parameters to prevent  
    150 #            # settings from static configuration.  These are set from the  
    151 #            # incoming XACML context 
    152 #            if min([queryAttrName.startswith(i) for i in  
    153 #                    self.__class__.DISALLOWED_ATTRIBUTE_QUERY_OPTNAMES]): 
    154 #                super(PIP, self).__setattr__(name, value) 
    155 #                 
    156 #            setattr(self.__attributeQueryBinding, queryAttrName, value) 
    157 #        else: 
    158 #            super(PIP, self).__setattr__(name, value)     
    159  
     184    def __setattr__(self, name, value): 
     185        """Enable setting of AttributeQuerySslSOAPBinding attributes from 
     186        names starting with attributeQuery.* / attributeQuery_*.  Addition for 
     187        setting these values from ini file 
     188        """ 
     189 
     190        # Coerce into setting AttributeQuerySslSOAPBinding attributes -  
     191        # names must start with 'attributeQuery\W' e.g. 
     192        # attributeQuery.clockSkewTolerance or attributeQuery_issuerDN 
     193        if name.startswith(self.__class__.ATTRIBUTE_QUERY_ATTRNAME): 
     194            queryAttrName = name[ 
     195                                self.__class__.ATTRIBUTE_QUERY_ATTRNAME_OFFSET:] 
     196             
     197            # Skip subject related parameters to prevent settings from static 
     198            # configuration.  These are set at runtime 
     199            if min([queryAttrName.startswith(i)  
     200                    for i in self.__class__.DISALLOWED_ATTRIBUTE_QUERY_OPTNAMES 
     201                    ]): 
     202                super(PIP, self).__setattr__(name, value) 
     203                 
     204            setattr(self.__attributeQueryBinding, queryAttrName, value) 
     205        else: 
     206            super(PIP, self).__setattr__(name, value)     
    160207     
    161208    def readMappingFile(self): 
     
    163210        """ 
    164211        mappingFile = open(self.mappingFilePath) 
    165         self.__attribute2AttributeAuthorityMap = dict() 
    166         for line in mappingFile.readline(): 
    167             if not line.startswith('#'): 
    168                 attributeId, attributeAuthorityURI = line.split() 
     212        for line in mappingFile.readlines(): 
     213            _line = path.expandvars(line).strip() 
     214            if _line and not _line.startswith('#'): 
     215                attributeId, attributeAuthorityURI = _line.split() 
    169216                self.__attribute2AttributeAuthorityMap[attributeId 
    170217                                                       ] = attributeAuthorityURI 
     
    182229                            (Request, type(context))) 
    183230         
    184         # TODO: Check for cached attributes for this subject (i.e. user) 
    185          
     231        # TODO: Check for cached attributes for this subject (i.e. user)         
    186232        # If none found send a query to the attribute authority 
    187          
    188         # TODO: Look-up the attribute authority corresponding to the query 
    189  
    190 #        attributeAuthorityURI = self.__attribute2AttributeAuthorityMap.get( 
    191 #                                                                attributeName) 
    192         attributeAuthorityURI = 'https://localhost:7443/AttributeAuthority' 
    193                          
     233 
     234        # Look up mapping from request attribute ID to Attribute Authority to 
     235        # query  
     236        attributeAuthorityURI = self.__attribute2AttributeAuthorityMap.get( 
     237                                            attributeDesignator.attributeId, 
     238                                            None) 
     239        if attributeAuthorityURI is None: 
     240            log.debug("No matching attribute authority endpoint found in " 
     241                      "mapping file %r for input attribute ID %r",  
     242                      self.mappingFilePath, 
     243                      attributeDesignator.attributeId) 
     244             
     245            return [] 
     246         
    194247        # Get subject from the request context 
    195         # TODO: parameterise data type setting 
    196248        subjectId = None 
    197249        for subject in context.subjects: 
    198250            for attribute in subject.attributes: 
    199                 if attribute.dataType == 'urn:esg:openid': 
     251                if attribute.attributeId == self.subjectAttributeId: 
    200252                    if len(attribute.attributeValues) != 1: 
    201253                        raise Exception("Expecting a single attribute value " 
     
    210262        else: 
    211263            # Keep a reference to the matching Subject instance 
    212             _subject = subject 
     264            xacmlCtxSubject = subject 
    213265             
    214266        # Get the id of the attribute to be queried for and add it to the SAML 
     
    218270        samlAttribute.name = attributeDesignator.attributeId 
    219271        samlAttribute.nameFormat = attributeFormat 
    220         attributeQueryBinding.query.attributes.append(samlAttribute) 
    221          
    222          
    223         attributeQueryBinding.subjectID = subjectId 
    224         response = attributeQueryBinding.send(uri=attributeAuthorityURI) 
     272        self.attributeQueryBinding.query.attributes.append(samlAttribute) 
     273         
     274        # Dispatch query 
     275        try: 
     276            self.attributeQueryBinding.subjectID = subjectId 
     277            self.attributeQueryBinding.subjectIdFormat = self.subjectAttributeId 
     278            response = self.attributeQueryBinding.send( 
     279                                                    uri=attributeAuthorityURI) 
     280        finally: 
     281            # !Ensure relevant query attributes are reset ready for any  
     282            # subsequent query! 
     283            self.attributeQueryBinding.subjectID = '' 
     284            self.attributeQueryBinding.subjectIdFormat = '' 
     285            self.attributeQueryBinding.query.attributes = TypedList( 
     286                                                                SamlAttribute) 
    225287         
    226288        # Unpack SAML assertion attribute values corresponding to the name  
     
    234296         
    235297        # Update the XACML request context subject with the new attributes 
    236         _subject.attributes.append(attributeValues) 
     298        xacmlCtxSubject.attributes.append(attributeValues) 
    237299         
    238300        # Return the attributes to the caller to comply with the interface 
Note: See TracChangeset for help on using the changeset viewer.