source: TI12-security/trunk/NDG_XACML/ndg/xacml/core/Copy of msi.py @ 6745

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDG_XACML/ndg/xacml/core/Copy of msi.py@6745
Revision 6745, 12.6 KB checked in by pjkersha, 10 years ago (diff)

Moved modules into core package

Line 
1"""NDG Security MSI Resource Policy module
2
3NERC Data Grid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "03/04/09"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__contact__ = "Philip.Kershaw@stfc.ac.uk"
9__license__ = "BSD - see LICENSE file in top-level directory"
10__contact__ = "Philip.Kershaw@stfc.ac.uk"
11__revision__ = "$Id: $"
12
13import logging
14log = logging.getLogger(__name__)
15
16import traceback
17import warnings
18from elementtree import ElementTree
19
20from ndg.security.common.utils import TypedList
21from ndg.security.common.utils.etree import QName
22from ndg.security.common.authz import (_AttrDict, SubjectBase, Subject,
23                                       SubjectRetrievalError)
24from ndg.security.common.authz.pip import (PIPBase, PIPAttributeQuery, 
25                                           PIPAttributeResponse)
26
27
28
29
30class TargetParseError(PolicyParseError):
31    """Error reading resource attributes from file"""
32
33import re
34   
35
36class AttributeParseError(PolicyParseError):
37    """Error parsing a Policy Attribute element"""
38   
39
40class Attribute(PolicyComponent):
41    """encapsulate a target attribute including the name and an Attribute
42    Authority from which user attribute information may be queried
43    """
44    NAME_LOCALNAME = "Name"
45    ATTRIBUTE_AUTHORITY_URI_LOCALNAME = "AttributeAuthorityURI"
46   
47    __slots__ = ('__name', '__attributeAuthorityURI')
48   
49    def __init__(self):
50        super(Attribute, self).__init__()
51        self.__name = ''
52        self.__attributeAuthorityURI = None
53
54    def __str__(self):
55        return self.__name
56   
57    def _getName(self):
58        return self.__name
59
60    def _setName(self, value):
61        if not isinstance(value, basestring):
62            raise TypeError('Expecting string type for "name"; got %r' %
63                            type(value))
64        self.__name = value
65
66    name = property(fget=_getName, 
67                    fset=_setName, 
68                    doc="Attribute name")
69       
70    def _getAttributeAuthorityURI(self):
71        return self.__attributeAuthorityURI
72
73    def _setAttributeAuthorityURI(self, value):
74        self.__attributeAuthorityURI = value
75
76    attributeAuthorityURI = property(_getAttributeAuthorityURI, 
77                                     _setAttributeAuthorityURI, 
78                                     doc="Attribute Authority URI")
79       
80    def parse(self, root):
81        """Parse from an ElementTree Element"""
82        self.xmlns = QName.getNs(root.tag)
83       
84        for elem in root:
85            localName = QName.getLocalPart(elem.tag)
86            if localName == Attribute.ATTRIBUTE_AUTHORITY_URI_LOCALNAME:
87                self.attributeAuthorityURI = elem.text.strip()
88               
89            elif localName == Attribute.NAME_LOCALNAME:
90                self.name = elem.text.strip()
91            else:
92                raise AttributeParseError("Invalid Attribute element name: %s" % 
93                                          localName)
94   
95    @classmethod
96    def Parse(cls, root):
97        """Parse from an ElementTree Element and return a new instance"""
98        resource = cls()
99        resource.parse(root)
100        return resource
101
102           
103class Request(object):
104    '''Request to send to a PDP'''
105#    __slots__ = ('__subject', '__resource')
106   
107    def __init__(self, subject=Subject(), resource=Resource()):
108        self.subject = subject
109        self.resource = resource
110
111    def _getSubject(self):
112        return self.__subject
113   
114    def _setSubject(self, subject):
115        if not isinstance(subject, SubjectBase):
116            raise TypeError("Expecting %r type for Request subject; got %r" %
117                            (Subject, type(subject)))
118        self.__subject = subject
119
120    subject = property(fget=_getSubject,
121                       fset=_setSubject,
122                       doc="Subject type object representing subject accessing "
123                           "a resource")
124
125    def _getResource(self):
126        return self.__resource
127   
128    def _setResource(self, resource):
129        if not isinstance(resource, Resource):
130            raise TypeError("Expecting %s for Request Resource; got %r" %
131                            (Resource.__class__.__name__, resource))
132        self.__resource = resource
133
134    resource = property(fget=_getResource,
135                        fset=_setResource,
136                        doc="Resource to be protected")
137#
138#    def __getstate__(self):
139#        '''Enable pickling'''
140#        _dict = {}
141#        for attrName in Request.__slots__:
142#            # Ugly hack to allow for derived classes setting private member
143#            # variables
144#            if attrName.startswith('__'):
145#                attrName = "_Request" + attrName
146#               
147#            _dict[attrName] = getattr(self, attrName)
148#           
149#        return _dict
150
151#    def __setstate__(self, attrDict):
152#        '''Enable pickling'''
153#        for attrName, val in attrDict.items():
154#            setattr(self, attrName, val)
155           
156
157class Response(object):
158    '''Response from a PDP'''
159    decisionValues = range(4)
160    (DECISION_PERMIT,
161     DECISION_DENY,
162     DECISION_INDETERMINATE,
163     DECISION_NOT_APPLICABLE) = decisionValues
164
165    # string versions of the 4 Decision types used for encoding
166    DECISIONS = ("Permit", "Deny", "Indeterminate", "NotApplicable")
167   
168    decisionValue2String = dict(zip(decisionValues, DECISIONS))
169   
170    def __init__(self, status, message=None):
171        self.__status = None
172        self.__message = None
173       
174        self.status = status
175        self.message = message
176
177    def _setStatus(self, status):
178        if status not in Response.decisionValues:
179            raise TypeError("Status %s not recognised" % status)
180       
181        self.__status = status
182       
183    def _getStatus(self):
184        return self.__status
185   
186    status = property(fget=_getStatus,
187                      fset=_setStatus,
188                      doc="Integer response code; one of %r" % decisionValues)
189
190    def _setMessage(self, message):
191        if not isinstance(message, (basestring, type(None))):
192            raise TypeError('Expecting string or None type for "message"; got '
193                            '%r' % type(message))
194       
195        self.__message = message
196       
197    def _getMessage(self):
198        return self.__message
199   
200    message = property(fget=_getMessage,
201                       fset=_setMessage,
202                       doc="Optional message associated with response")
203
204           
205class PDP(object):
206    """Policy Decision Point"""
207   
208    def __init__(self, policy, pip):
209        """Read in a file which determines access policy"""
210        self.policy = policy
211        self.pip = pip
212
213    def _getPolicy(self):
214        if self.__policy is None:
215            raise TypeError("Policy object has not been initialised")
216        return self.__policy
217   
218    def _setPolicy(self, policy):
219        if not isinstance(policy, (Policy, None.__class__)):
220            raise TypeError("Expecting %s or None type for PDP policy; got %r"%
221                            (Policy.__class__.__name__, policy))
222        self.__policy = policy
223
224    policy = property(fget=_getPolicy,
225                      fset=_setPolicy,
226                      doc="Policy type object used by the PDP to determine "
227                          "access for resources")
228
229    def _getPIP(self):
230        if self.__pip is None:
231            raise TypeError("PIP object has not been initialised")
232       
233        return self.__pip
234   
235    def _setPIP(self, pip):
236        if not isinstance(pip, (PIPBase, None.__class__)):
237            raise TypeError("Expecting %s or None type for PDP PIP; got %r"%
238                            (PIPBase.__class__.__name__, pip))
239        self.__pip = pip
240
241    pip = property(fget=_getPIP,
242                   fset=_setPIP,
243                   doc="Policy Information Point - PIP type object used by "
244                       "the PDP to retrieve user attributes")
245   
246    def evaluate(self, request):
247        '''Make access control decision'''
248       
249        if not isinstance(request, Request):
250            raise TypeError("Expecting %s type for request; got %r" %
251                            (Request.__class__.__name__, request))
252       
253        # Look for matching targets to the given resource
254        resourceURI = request.resource[Resource.URI_NS]
255        matchingTargets = [target for target in self.policy.targets
256                           if target.regEx.match(resourceURI) is not None]
257        numMatchingTargets = len(matchingTargets)
258        if numMatchingTargets == 0:
259            log.debug("PDP.evaluate: granting access - no targets matched "
260                      "the resource URI path [%s]", 
261                      resourceURI)
262            return Response(status=Response.DECISION_PERMIT)
263       
264        # Iterate through matching targets checking for user access
265        request.subject[Subject.ROLES_NS] = []
266        permitForAllTargets = [Response.DECISION_PERMIT]*numMatchingTargets
267       
268        # Keep a look-up of the decisions for each target
269        status = []
270       
271        # Make a query object for querying the Policy Information Point
272        attributeQuery = PIPAttributeQuery()
273        attributeQuery[PIPAttributeQuery.SUBJECT_NS] = request.subject
274       
275        # Keep a cache of queried Attribute Authorities to avoid calling them
276        # multiple times
277        queriedAttributeAuthorityURIs = []
278       
279        # Iterate through the targets gathering user attributes from the
280        # relevant attribute authorities
281        for matchingTarget in matchingTargets:
282           
283            # Make call to the Policy Information Point to pull user
284            # attributes applicable to this resource
285            for attribute in matchingTarget.attributes:
286                if (attribute.attributeAuthorityURI in 
287                    queriedAttributeAuthorityURIs): 
288                    continue
289                         
290                attributeQuery[
291                    PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS
292                ] = attribute.attributeAuthorityURI
293           
294                # Exit from function returning indeterminate status if a
295                # problem occurs here
296                try:
297                    attributeResponse = self.pip.attributeQuery(attributeQuery)
298                   
299                except SubjectRetrievalError, e:
300                    # i.e. a defined exception within the scope of this
301                    # module
302                    log.error("SAML Attribute Query %s: %s", 
303                              type(e), traceback.format_exc())
304                    return Response(Response.DECISION_INDETERMINATE, 
305                                    message=traceback.format_exc())
306                               
307                except Exception, e:
308                    log.error("SAML Attribute Query %s: %s", 
309                              type(e), traceback.format_exc())
310                    return Response(Response.DECISION_INDETERMINATE,
311                                    message="An internal error occurred")
312                               
313                # Accumulate attributes retrieved from multiple attribute
314                # authorities
315                request.subject[Subject.ROLES_NS] += attributeResponse[
316                                                            Subject.ROLES_NS]
317               
318            # Match the subject's attributes against the target
319            # One of any rule - at least one of the subject's attributes
320            # must match one of the attributes restricting access to the
321            # resource.
322            log.debug("PDP.evaluate: Matching subject attributes %r against "
323                      "resource attributes %r ...", 
324                      request.subject[Subject.ROLES_NS],
325                      matchingTarget.attributes)
326           
327            status.append(PDP._match(matchingTarget.attributes, 
328                                     request.subject[Subject.ROLES_NS]))
329           
330        # All targets must yield permit status for access to be granted
331        if status == permitForAllTargets:
332            return Response(Response.DECISION_PERMIT)
333        else:   
334            return Response(Response.DECISION_DENY,
335                            message="Insufficient privileges to access the "
336                                    "resource")
337       
338    @staticmethod
339    def _match(resourceAttr, subjectAttr):
340        """Helper method to iterate over user and resource attributes
341        If one at least one match is found, a permit response is returned
342        """
343        for attr in resourceAttr:
344            if attr.name in subjectAttr:
345                return Response.DECISION_PERMIT
346           
347        return Response.DECISION_DENY
348
349       
Note: See TracBrowser for help on using the repository browser.