source: TI12-security/trunk/NDG_XACML/ndg/xacml/core/context/pdp.py @ 6797

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDG_XACML/ndg/xacml/core/context/pdp.py@6797
Revision 6797, 17.3 KB checked in by pjkersha, 9 years ago (diff)

Added AttributeValueFactory? to generate <type>AttributeValue? classes.

Line 
1"""NDG Security Policy Decision Point type definition
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "25/02/10"
7__copyright__ = "(C) 2010 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: $"
12import logging
13log = logging.getLogger(__name__)
14
15import traceback
16
17from ndg.xacml.core.context.exceptions import XacmlContextError
18from ndg.xacml.core.context.pdpinterface import PDPInterface
19from ndg.xacml.core.policy import Policy
20from ndg.xacml.core.apply import Apply
21from ndg.xacml.core.context.request import Request
22from ndg.xacml.core.context.response import Response
23from ndg.xacml.core.context.result import Result, Decision
24from ndg.xacml.core.context.result import StatusCode
25from ndg.xacml.core.functions import FunctionMap
26from ndg.xacml.core.exceptions import (UnsupportedStdFunctionError,
27                                       UnsupportedFunctionError)     
28       
29
30class PDPError(XacmlContextError):
31    """Base class for PDP class exceptions"""
32     
33   
34class UnsupportedElementError(PDPError):
35    """Element type is not supported in this implementation"""
36    def __init__(self, *arg, **kw):
37        super(UnsupportedElementError, self).__init__(*arg, **kw)
38        self.response.results[0].status.statusCode.value = \
39                                                    StatusCode.SYNTAX_ERROR
40
41
42class UnsupportedStdElementError(UnsupportedElementError):
43    """Element type is part of the XACML spec. but is not supported in this
44    implementation""" 
45   
46   
47class PDP(PDPInterface):
48    """A XACML Policy Decision Point implementation.  It supports the use of a
49    single policy but not policy sets
50    """
51    __slots__ = ('__policy', '__request')
52    TARGET_CHILD_ATTRS = ('subjects', 'resources', 'actions', 'environments')
53   
54    def __init__(self, policy=None):
55        """
56        @param policy: policy object for PDP to use to apply access control
57        decisions, may be omitted.
58        @type policy: ndg.xacml.core.policy.Policy / None
59        """
60        self.__policy = None
61        if policy is not None:
62            self.policy = policy
63           
64        self.__functionMap = FunctionMap.withLoadedMap()
65           
66        self.__request = None
67
68    @classmethod
69    def fromPolicySource(cls, source, readerFactory):
70        """Create a new PDP instance with a given policy
71        @param source: source for policy
72        @type source: type (dependent on the reader set, it could be for example
73        a file path string, file object, XML element instance)
74        @param readerFactory: reader factory returns the reader to use to read
75        this policy
76        @type readerFactory: ndg.xacml.parsers.AbstractReader derived type
77        """           
78        pdp = cls()
79        pdp.policy = Policy.fromSource(source, readerFactory)
80        return pdp
81   
82    @property
83    def policy(self):
84        """policy object for PDP to use to apply access control decisions"""
85        return self.__policy
86   
87    @policy.setter
88    def policy(self, value):
89        '''policy object for PDP to use to apply access control decisions'''
90        if not isinstance(value, Policy):
91            raise TypeError('Expecting %r derived type for "policy" input; got '
92                            '%r instead' % (Policy, type(value)))
93        self.__policy = value
94
95    @property
96    def request(self):
97        """request context"""
98        return self.__request
99   
100    @request.setter
101    def request(self, value):
102        '''request context'''
103        if not isinstance(value, Request):
104            raise TypeError('Expecting %r derived type for "request" '
105                            'input; got %r instead' % (Request, 
106                                                       type(value)))
107        self.__request = value
108                                       
109    def evaluate(self, request):
110        """Make an access control decision for the given request based on the
111        policy set
112       
113        @param request: XACML request context
114        @type request: ndg.xacml.core.context.request.Request
115        @return: XACML response instance
116        @rtype: ndg.xacml.core.context.response.Response
117        """
118        response = Response()
119        result = Result.createInitialised(decision=Decision.NOT_APPLICABLE)
120        response.results.append(result)
121       
122        try:
123            self.request = request
124           
125        except AttributeError, e:
126            log.error(str(e))
127            result.decision = Decision.INDETERMINATE
128            return response
129           
130        # Exception block around all rule processing in order to set
131        # INDETERMINATE response from any exceptions raised
132        try:
133            log.debug('Checking policy target for match with request...')
134           
135           
136            if not self.matchTarget(self.policy.target, request):
137                log.debug('No match for policy target setting %r decision',
138                          Decision.NOT_APPLICABLE_STR)
139               
140                result.decision = Decision.NOT_APPLICABLE
141                return response
142           
143            log.debug('Request matches the Policy target')
144           
145            # Check rules
146            ruleStatusValues = [False]*len(self.policy.rules)
147            for i, rule in enumerate(self.policy.rules):
148                log.debug('Checking policy rule %r for match...', rule.id)
149                if not self.matchTarget(rule.target, request):
150                    log.debug('No match to request context for target in rule '
151                              '%r', rule.id)
152                    ruleStatusValues[i] = True
153                    continue   
154               
155                # Apply the condition
156                ruleStatusValues[i] = self.evaluateCondition(rule.condition) 
157                     
158        except PDPError, e:
159            log.error('Exception raised evaluating request context, returning '
160                      '%r decision:%s', 
161                      e.response.results[0].decision, 
162                      traceback.format_exc())
163           
164            result = e.response.results[0]
165           
166        except Exception:
167            # Catch all so that nothing is handled from within the scope of this
168            # method
169            log.error('No PDPError type exception raised evaluating request '
170                      'context, returning %r decision:%s', 
171                      Decision.INDETERMINATE_STR, 
172                      traceback.format_exc()) 
173                       
174            result.decision = Decision.INDETERMINATE
175            result.status.statusCode.value = StatusCode.PROCESSING_ERROR
176           
177        return response
178           
179    def matchTarget(self, target, request):
180        """Generic method to match a <Target> element to the request context
181       
182        @param target: XACML target element
183        @type target: ndg.xacml.core.target.Target
184        @param request: XACML request context
185        @type request: ndg.xacml.core.context.request.Request
186        @return: True if request context matches the given target,
187        False otherwise
188        @rtype: bool
189        """
190        if target is None:
191            log.debug('No target set so no match with request context')
192            return False
193       
194        # From section 5.5 of the XACML 2.0 Core Spec:
195        #
196        # For the parent of the <Target> element to be applicable to the
197        # decision request, there MUST be at least one positive match between
198        # each section of the <Target> element and the corresponding section of
199        # the <xacml-context:Request> element.
200        #
201        # Also, 7.6:
202        #
203        # The target value SHALL be "Match" if the subjects, resources, actions
204        # and environments specified in the target all match values in the
205        # request context.
206        statusValues = [False]*len(self.__class__.TARGET_CHILD_ATTRS) 
207       
208        # Iterate for target subjects, resources, actions and environments
209        # elements
210        for i, attrName in enumerate(self.__class__.TARGET_CHILD_ATTRS):
211            # If any one of the <Target> children is missing then it counts as
212            # a match e.g. for <Subjects> child element - Section 5.5:
213            #
214            # <Subjects> [Optional] Matching specification for the subject
215            # attributes in the context. If this element is missing,
216            # then the target SHALL match all subjects.
217            targetElem = getattr(target, attrName)
218            if len(targetElem) == 0:
219                statusValues[i] = True
220                continue
221           
222            # Iterate over each for example, subject in the list of subjects:
223            # <Target>
224            #     <Subjects>
225            #          <Subject>
226            #              ...
227            #          </Subject>
228            #          <Subject>
229            #              ...
230            #          </Subject>
231            #     ...
232            # or resource in the list of resources and so on
233            for targetSubElem in targetElem:
234               
235                # For the given subject/resource/action/environment check for a
236                # match with the equivalent in the request
237                requestElem = getattr(request, attrName) 
238                for requestSubElem in requestElem:
239                    if self.matchTargetChild(targetSubElem, requestSubElem):
240                        # Within the list of e.g. subjects if one subject
241                        # matches then this counts as a subject match overall
242                        # for this target
243                        statusValues[i] = True
244 
245        # Target matches if all the children (i.e. subjects, resources, actions
246        # and environment sections) have at least one match.  Otherwise it
247        # doesn't count as a match
248        return all(statusValues)
249   
250    def matchTargetChild(self, targetChild, requestChild):
251        """Match a request child element (a <Subject>, <Resource>, <Action> or
252        <Environment>) with the corresponding target's <Subject>, <Resource>,
253        <Action> or <Environment>.
254       
255        @param targetChild: Target Subject, Resource, Action or Environment
256        object
257        @type targetChild: ndg.xacml.core.TargetChildBase
258        @param requestChild: Request Subject, Resource, Action or Environment
259        object
260        @type requestChild: ndg.xacml.core.context.RequestChildBase
261        @return: True if request context matches something in the target
262        @rtype: bool
263        @raise UnsupportedElementError: AttributeSelector processing is not
264        currently supported.  If an AttributeSelector is found in the policy,
265        this exception will be raised.
266        @raise UnsupportedStdFunctionError: policy references a function type
267        which is in the XACML spec. but is not supported by this implementation
268        @raise UnsupportedFunctionError: policy references a function type which
269        is not supported by this implementation
270        """
271        if targetChild is None:
272            # Default if target child is not set is to match all children
273            return True
274       
275        matchStatusValues = [True]*len(targetChild.matches)
276       
277        # Section 7.6
278        #
279        # A subject, resource, action or environment SHALL match a value in the
280        # request context if the value of all its <SubjectMatch>,
281        # <ResourceMatch>, <ActionMatch> or <EnvironmentMatch> elements,
282        # respectively, are "True".
283        #
284        # e.g. for <SubjectMatch>es in <Subject> ...
285        for childMatch, matchStatus in zip(targetChild.matches, 
286                                           matchStatusValues):
287           
288            # Get the match function from the Match ID
289            matchFunc = self.__functionMap.get(childMatch.matchId)
290            if matchFunc is NotImplemented:
291                raise UnsupportedStdFunctionError('No match function '
292                                                  'implemented for MatchId="%s"'
293                                                  % childMatch.matchId)
294            elif matchFunc is None:
295                raise UnsupportedFunctionError('Match function namespace %r is '
296                                               'not recognised' % 
297                                               childMatch.matchId)
298               
299            matchAttributeValue = childMatch.attributeValue.value
300           
301            # Create a match function based on the presence or absence of an
302            # AttributeDesignator or AttributeSelector
303            if childMatch.attributeDesignator is not None:
304                _attributeMatch = self.attributeDesignatorMatchFuncFactory(
305                                                matchFunc,
306                                                childMatch.attributeValue.value,
307                                                childMatch.attributeDesignator)
308               
309            elif childMatch.attributeSelector is not None:
310                # Nb. This will require that the request provide a reference to
311                # it's XML representation and an abstraction of the XML parser
312                # for executing XPath searches into that representation
313                raise UnsupportedElementError('This PDP implementation does '
314                                              'not support <AttributeSelector> '
315                                              'elements')
316            else:
317                _attributeMatch = lambda requestChildAttribute: (
318                    matchFunc.evaluate(matchAttributeValue, 
319                                    requestChildAttribute.attributeValue.value)
320                )
321       
322            # Iterate through each attribute in the request in turn matching it
323            # against the target using the generated _attributeMatch function
324            #
325            # Any Match element NOT matching will result in an overall status of
326            # no match.
327            #
328            # Continue iterating through the whole list even if a False status
329            # is found.  The other attributes need to be checked in case an
330            # error occurs.  In this case the top-level PDP exception handling
331            # block will catch it and set an overall decision of INDETERMINATE
332            attrMatchStatusValues = [False]*len(requestChild.attributes)
333           
334            for attribute, attrMatchStatus in zip(requestChild.attributes, 
335                                                  attrMatchStatusValues):
336                attrMatchStatus = _attributeMatch(attribute)
337                if attrMatchStatus == True:
338                    if log.getEffectiveLevel() <= logging.DEBUG:
339                        log.debug('Request attribute %r set to %r matches '
340                                  'target',
341                                  attribute.attributeId,
342                                  [a.value for a in attribute.attributeValues])
343                       
344            matchStatus = all(attrMatchStatusValues)
345       
346        # Any match => overall match     
347        return any(matchStatusValues)
348               
349    def attributeDesignatorMatchFuncFactory(self,
350                                            matchFunc,
351                                            matchAttributeValue, 
352                                            attributeDesignator):
353        """Define a match function to match a given request attribute against
354        the input attribute value and AttributeDesignator defined in a policy
355        target
356        """           
357        attributeId = attributeDesignator.attributeId
358        dataType = attributeDesignator.dataType
359       
360        # Issuer is an optional match - see core spec. 7.2.4
361        issuer = attributeDesignator.issuer
362        if issuer is not None:
363            # Issuer found - set lambda to match this against the
364            # issuer setting in the request
365            _issuerMatch = lambda requestChildIssuer: (
366                                                issuer == requestChildIssuer)
367        else:
368            # No issuer set - lambda returns True regardless
369            _issuerMatch = lambda requestChildIssuer: True
370       
371       
372        _attributeMatch = lambda attribute: (
373            any([matchFunc.evaluate(matchAttributeValue, attrVal.value) 
374                 for attrVal in attribute.attributeValues]) and
375            attribute.attributeId == attributeId and
376            attribute.dataType == dataType and
377            _issuerMatch(attribute.issuer)
378        )
379       
380        return _attributeMatch
381   
382    def evaluateCondition(self, condition):
383        """Evaluate a rule condition
384        @param condition: rule condition
385        @type condition: ndg.xacml.core.condition.Condition
386        @return: True/False status for whether the rule condition matched or
387        not
388        @rtype: bool
389        """
390       
391        # Spec:
392        #
393        # "The condition value SHALL be "True" if the <Condition> element is
394        # absent"
395        if condition is None:
396            return True
397       
398        applyElem = condition.expression
399        if not isinstance(applyElem, Apply):
400            raise UnsupportedElementError('%r type <Condition> sub-element '
401                                          'processing is not supported in this '
402                                          'implementation' % applyElem)
403         
404        result = applyElem.evaluate(self.request)
405
406
407
Note: See TracBrowser for help on using the repository browser.