source: TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/pip/saml_pip.py @ 7310

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/pip/saml_pip.py@7310
Revision 7310, 9.9 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 2: XACML-Security Integration

  • progress with SAML attribute query interface to PIP.
Line 
1"""Module for XACML Policy Information Point with SAML interface to
2Attribute Authority
3
4"""
5__author__ = "P J Kershaw"
6__date__ = "06/08/10"
7__copyright__ = "(C) 2010 Science and Technology Facilities Council"
8__license__ = "BSD - see LICENSE file in top-level directory"
9__contact__ = "Philip.Kershaw@stfc.ac.uk"
10__revision__ = '$Id:$'
11import logging
12log = logging.getLogger(__name__)
13
14from os import path
15from ConfigParser import SafeConfigParser
16
17from ndg.xacml.core.context.pipinterface import PIPInterface
18from ndg.xacml.core.context.request import Request
19
20from ndg.saml.saml2.core import (AttributeQuery as SamlAttributeQuery, 
21                                 Attribute as SamlAttribute)
22from ndg.saml.utils import TypedList as SamlTypedList
23from ndg.saml.saml2.binding.soap.client.attributequery import \
24                                            AttributeQuerySslSOAPBinding
25                                           
26from ndg.security.common.X509 import X509Cert
27
28
29class PIP(PIPInterface):
30    '''Policy Information Point enables XACML PDP to query for additional user
31    attrbiutes.  The PDP does this indirectly via the Context Handler
32    '''
33    # Subject attributes makes no sense for external configuration - these
34    # are set at run time based on the given subject identity
35    DISALLOWED_ATTRIBUTE_QUERY_OPTNAMES = (
36        AttributeQuerySslSOAPBinding.SUBJECT_ID_OPTNAME,
37        AttributeQuerySslSOAPBinding.QUERY_ATTRIBUTES_ATTRNAME
38    )
39    ATTRIBUTE_QUERY_ATTRNAME = 'attributeQuery'
40    LEN_ATTRIBUTE_QUERY_ATTRNAME = len(ATTRIBUTE_QUERY_ATTRNAME)
41   
42    # +1 allows for '.' or other separator e.g.
43    # pip.attributeQuery.issuerName
44    #                   ^
45    ATTRIBUTE_QUERY_ATTRNAME_OFFSET = LEN_ATTRIBUTE_QUERY_ATTRNAME + 1
46   
47    __slots__ = ('__mappingFilePath',)
48   
49    def __init__(self):
50        '''Initialise settings for connection to an Attribute Authority'''
51        self.__attributeQueryBinding = AttributeQuerySslSOAPBinding()   
52        self.__mappingFilePath = None
53
54    def _getMappingFilePath(self):
55        return self.__mappingFilePath
56
57    def _setMappingFilePath(self, value):
58        if not isinstance(value, basestring):
59            raise TypeError('Expecting string type for "mappingFilePath"; got '
60                            '%r' % type(value))
61        self.__mappingFilePath = value
62
63    mappingFilePath = property(_getMappingFilePath, 
64                               _setMappingFilePath, 
65                               doc="Mapping File maps attribute IDs to the "
66                                   "Attribute Authorities from which they may "
67                                   "be queried for")
68
69    @classmethod
70    def fromConfig(cls, cfg, **kw):
71        '''Alternative constructor makes object from config file settings
72        @type cfg: basestring /ConfigParser derived type
73        @param cfg: configuration file path or ConfigParser type object
74        @rtype: ndg.saml.saml2.binding.soap.client.SOAPBinding
75        @return: new instance of this class
76        '''
77        obj = cls()
78        obj.parseConfig(cfg, **kw)
79       
80        return obj
81   
82    def __setAttr(self, optName, val): 
83        """Helper method to differentiate attribute query binding option names
84        from other config options
85       
86        @param optName: config file option name
87        @type optName: basestring
88        @param val: corresponding value
89        @type val: basestring
90        """
91        if optName.startswith(self.__class__.ATTRIBUTE_QUERY_ATTRNAME):
92            setattr(self, 
93                    optName[self.__class__.ATTRIBUTE_QUERY_ATTRNAME_OFFSET:],
94                    val)
95        else:
96            setattr(self, optName, val)
97
98    def parseConfig(self, cfg, prefix='', section='DEFAULT'):
99        '''Read config settings from a file, config parser object or dict
100       
101        @type cfg: basestring / ConfigParser derived type / dict
102        @param cfg: configuration file path or ConfigParser type object
103        @type prefix: basestring
104        @param prefix: prefix for option names e.g. "attributeQuery."
105        @type section: baestring
106        @param section: configuration file section from which to extract
107        parameters.
108        ''' 
109        if isinstance(cfg, basestring):
110            cfgFilePath = path.expandvars(cfg)
111            _cfg = SafeConfigParser()
112            _cfg.read(cfgFilePath)
113            items = _cfg.items(section)
114           
115        elif isinstance(cfg, ConfigParser):
116            items = cfg.items(section)
117         
118        elif isinstance(cfg, dict):
119            items = cfg.items()     
120        else:
121            raise AttributeError('Expecting basestring, ConfigParser or dict '
122                                 'type for "cfg" attribute; got %r type' % 
123                                 type(cfg))
124       
125        prefixLen = len(prefix)
126       
127        for optName, val in items:
128            if prefix:
129                # Filter attributes based on prefix
130                if optName.startswith(prefix):
131                    self.__setAttr(optName[prefixLen:], val)
132            else:
133                # No prefix set - attempt to set all attributes   
134                self.__setAttr(optName, val)
135                           
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
160   
161    def readMappingFile(self):
162        """Read the file which maps attribute names to Attribute Authorities
163        """
164        mappingFile = open(self.mappingFilePath)
165        self.__attribute2AttributeAuthorityMap = dict()
166        for line in mappingFile.readline():
167            if not line.startswith('#'):
168                attributeId, attributeAuthorityURI = line.split()
169                self.__attribute2AttributeAuthorityMap[attributeId
170                                                       ] = attributeAuthorityURI
171       
172    def attributeQuery(self, context, attributeDesignator):
173        """Query this PIP for attributes.   
174       
175        @param context: the request context
176        @type context: ndg.xacml.core.context.request.Request
177        @rtype:
178        @return: attribute values found for query subject
179        """
180        if not isinstance(context, Request):
181            raise TypeError('Expecting %r type for context input; got %r' %
182                            (Request, type(context)))
183       
184        # TODO: Check for cached attributes for this subject (i.e. user)
185       
186        # 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                       
194        # Get subject from the request context
195        # TODO: parameterise data type setting
196        subjectId = None
197        for subject in context.subjects:
198            for attribute in subject.attributes:
199                if attribute.dataType == 'urn:esg:openid':
200                    if len(attribute.attributeValues) != 1:
201                        raise Exception("Expecting a single attribute value "
202                                        "for query subject ID")
203                    subjectId = attribute.attributeValues[0].value
204                    break
205       
206        if subjectId is None:
207            # TODO: parameterise data type setting
208            raise Exception('No subject found of type %r in request context' %
209                            'urn:esg:openid')
210        else:
211            # Keep a reference to the matching Subject instance
212            _subject = subject
213           
214        # Get the id of the attribute to be queried for and add it to the SAML
215        # query
216        attributeFormat = attributeDesignator.dataType
217        samlAttribute = SamlAttribute()
218        samlAttribute.name = attributeDesignator.attributeId
219        samlAttribute.nameFormat = attributeFormat
220        attributeQueryBinding.query.attributes.append(samlAttribute)
221       
222       
223        attributeQueryBinding.subjectID = subjectId
224        response = attributeQueryBinding.send(uri=attributeAuthorityURI)
225       
226        # Unpack SAML assertion attribute values corresponding to the name
227        # format specified
228        attributeValues = []
229        for assertion in response.assertions:
230            for statement in assertion.attributeStatements:
231                for attribute in statement.attributes:
232                    if attribute.nameFormat == attributeFormat:
233                        attributeValues.append(attribute.attributeValues)
234       
235        # Update the XACML request context subject with the new attributes
236        _subject.attributes.append(attributeValues)
237       
238        # Return the attributes to the caller to comply with the interface
239        return attributeValues
240       
Note: See TracBrowser for help on using the repository browser.