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

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

Refactored functions package to enable dynamic creation of function classes for all the XACML primitive types for any given function e.g. equal module implements EqualBase? and dynamically creates the <type>-equal classes for all the types: string, AnyURI, Boolean etc.

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