source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/xacml/cond.py @ 5375

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/xacml/cond.py@5375
Revision 5375, 23.1 KB checked in by pjkersha, 12 years ago (diff)

Refactored XACML code:

  • fixes to Policy.getInstance parser method
  • moving Factory and Evaluatable classes from cond module into their own modules
Line 
1"""XACML cond module contains condition function classes
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "02/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$"
12import logging
13log = logging.getLogger(__name__)
14
15from ndg.security.common.authz.xacml.cond.eval import Evaluatable
16from ndg.security.common.authz.xacml.attr import AnyURIAttribute, \
17    Base64BinaryAttribute, BooleanAttribute, DateAttribute, DateTimeAttribute,\
18    DayTimeDurationEqual, DoubleAttribute, HexBinaryAttribute, \
19    IntegerAttribute, RFC822NameAttribute, StringAttribute, TimeAttribute, \
20    X500NameAttribute, YearMonthDurationAttribute
21
22
23class Apply(Evaluatable):
24    '''Represents the XACML ApplyType and ConditionType XML types.'''
25
26    def __init__(self, function, evals, bagFunction=None, isCondition=False):
27        '''Constructs an Apply object. Throws an
28        IllegalArgumentException if the given parameter list
29        isn't valid for the given function.
30       
31        @param function the Function to use in evaluating the elements in the
32        apply
33        @param evals the contents of the apply which will be the parameters
34        to the function, each of which is an Evaluatable
35        @param bagFunction the higher-order function to use
36        @param isCondition Rrue if this Apply is a Condition, False otherwise
37        '''
38   
39        # check that the given inputs work for the function
40        inputs = evals
41        if bagFunction is not None:
42            inputs = [bagFunction]
43            inputs += evals
44       
45        function.checkInputs(inputs)
46
47        # if everything checks out, then store the inputs
48        self.function = function
49        self.evals = tuple(evals)
50        self.bagFunction = bagFunction
51        self.isCondition = isCondition
52   
53   
54    @staticmethod
55    def getConditionInstance(root, xpathVersion):
56        '''Returns an instance of an Apply based on the given DOM
57        root node. This will actually return a special kind of
58        Apply, namely an XML ConditionType, which is the root
59        of the condition logic in a RuleType. A ConditionType is the same
60        as an ApplyType except that it must use a FunctionId that returns
61        a boolean value.
62       
63        @param root the DOM root of a ConditionType XML type
64        @param xpathVersion the XPath version to use in any selectors or XPath
65                            functions, or null if this is unspecified (ie, not
66                            supplied in the defaults section of the policy)
67       
68        '''
69        raise NotImplementedError()
70         
71    def getInstance(self, 
72                    root, 
73                    factory=None, 
74                    isCondition=False, 
75                    xpathVersion=None):
76        '''Returns an instance of Apply based on the given DOM root.
77       
78        @param root the DOM root of an ApplyType XML type
79        @param xpathVersion the XPath version to use in any selectors or XPath
80                            functions, or null if this is unspecified (ie, not
81                            supplied in the defaults section of the policy)'''
82       
83        raise NotImplementedError()
84   
85    @staticmethod
86    def getFunction(root, version, factory):
87        '''Helper method that tries to get a function instance'''
88        raise NotImplementedError()
89           
90    def getFunction(self):
91        '''Returns the Function used by this Apply.
92       
93        @return the Function'''
94        return function
95   
96    def getChildren(self):
97        '''Returns the List of children for this Apply.
98        The List contains Evaluatables. The list is
99        unmodifiable, and may be empty.
100       
101        @return a List of Evaluatables'''
102        return self.evals
103   
104    def getHigherOrderFunction(self):
105        '''Returns the higher order bag function used by this Apply
106        if it exists, or null if no higher order function is used.
107       
108        @return the higher order Function or null'''
109        return self.bagFunction
110   
111    def isCondition(self):
112        '''Returns whether or not this ApplyType is actually a ConditionType.
113       
114        @return whether or not this represents a ConditionType'''
115        return isCondition
116
117    def evaluate(self, context):
118        '''Evaluates the apply object using the given function. This will in
119        turn call evaluate on all the given parameters, some of which may be
120        other Apply objects.
121       
122        @param context the representation of the request
123       
124        @return the result of trying to evaluate this apply object'''
125        parameters = self.evals
126
127        # see if there is a higher-order function in here
128        if bagFunction != None:
129            # this is a special case, so we setup the parameters, starting
130            # with the function
131            parameters = [bagFunction]
132
133            # now we evaluate all the parameters, returning INDETERMINATE
134            # if that's what any of them return, and otherwise tracking
135            # all the AttributeValues that get returned
136            for eval in self.evals:
137                result = eval.evaluate(context)
138               
139                # in a higher-order case, if anything is INDETERMINATE, then
140                # we stop right away
141                if result.indeterminate():
142                    return result
143
144                parameters.add(result.getAttributeValue())
145           
146        # now we can call the base function
147        return function.evaluate(parameters, context)
148         
149    def getType(self):
150        '''Returns the type of attribute that this object will return on a call
151        to evaluate. In practice, this will always be the same as
152        the result of calling getReturnType on the function used
153        by this object.
154       
155        @return the type returned by evaluate'''
156        return self.function.getReturnType()
157     
158    def evaluatesToBag(self):
159        '''Returns whether or not the Function will return a bag
160        of values on evaluation.
161       
162        @return true if evaluation will return a bag of values, false otherwise
163        '''
164        return self.function.returnsBag()
165
166    def encode(self, output, indenter):
167        '''Encodes this Apply into its XML representation and
168        writes this encoding to the given OutputStream with
169        indentation.
170       
171        @param output a stream into which the XML-encoded data is written
172        @param indenter an object that creates indentation strings'''
173        raise NotImplementedError()
174       
175class Function(object):
176    '''Interface that all functions in the system must implement.'''
177 
178    def evaluate(self, inputs, context):
179        '''Evaluates the Function using the given inputs.
180        The List contains Evaluatables which are all
181        of the correct type if the Function has been created as
182        part of an Apply or TargetMatch, but which
183        may otherwise be invalid. Each parameter should be evaluated by the
184        Function, unless this is a higher-order function (in
185        which case the Apply has already evaluated the inputs
186        to check for any INDETERMINATE conditions), or the Function
187        doesn't need to evaluate all inputs to determine a result (as in the
188        case of the or function). The order of the List is
189        significant, so a Function should have a very good reason
190        if it wants to evaluate the inputs in a different order.
191        <p>
192        Note that if this is a higher-order function, like any-of, then
193        the first argument in the List will actually be a Function
194        object representing the function to apply to some bag. In this case,
195        the second and any subsequent entries in the list are
196        AttributeValue objects (no INDETERMINATE values are
197        allowed, so the function is not given the option of dealing with
198        attributes that cannot be resolved). A function needs to know if it's
199        a higher-order function, and therefore whether or not to look for
200        this case. Also, a higher-order function is responsible for checking
201        that the inputs that it will pass to the Function
202        provided as the first parameter are valid, ie. it must do a
203        checkInputs on its sub-function when
204        checkInputs is called on the higher-order function.
205       
206        @param inputs the List of inputs for the function
207        @param context the representation of the request
208       
209        @return a result containing the AttributeValue computed
210                when evaluating the function, or Status
211                specifying some error condition'''
212        raise NotImplementedError()
213
214
215    def getIdentifier(self):
216        '''Returns the identifier of this function as known by the factories.
217        In the case of the standard XACML functions, this will be one of the
218        URIs defined in the standard namespace. This function must always
219        return the complete namespace and identifier of this function.
220       
221        @return the function's identifier'''
222        raise NotImplementedError()
223
224    def getReturnType(self):
225        '''Provides the type of AttributeValue that this function
226        returns from evaluate in a successful evaluation.
227       
228        @return the type returned by this function
229        '''
230        raise NotImplementedError()
231 
232    def returnsBag(self):
233        '''Tells whether this function will return a bag of values or just a
234        single value.
235       
236        @return true if evaluation will return a bag, false otherwise'''
237        raise NotImplementedError()
238
239    def checkInputs(self, inputs):
240        '''Checks that the given inputs are of the right types, in the right
241        order, and are the right number for this function to evaluate. If
242        the function cannot accept the inputs for evaluation, an
243        IllegalArgumentException is thrown.
244       
245        @param inputs a list of Evaluatables, with the first argument being a
246        Function if this is a higher-order function
247       
248        @throws TypeError if the inputs do match what the function accepts for
249        evaluation
250        '''
251        raise NotImplementedError()
252
253    def checkInputsNoBag(self, inputs):
254        '''Checks that the given inputs are of the right types, in the right
255        order, and are the right number for this function to evaluate. If
256        the function cannot accept the inputs for evaluation, an
257        IllegalArgumentException is thrown. Unlike the other
258        checkInput method in this interface, this assumes that
259        the parameters will never provide bags of values. This is useful if
260        you're considering a target function which has a designator or
261        selector in its input list, but which passes the values from the
262        derived bags one at a time to the function, so the function doesn't
263        have to deal with the bags that the selector or designator
264        generates.
265       
266        @param inputs a list of Evaluatables, with the first argument being a
267        Function if this is a higher-order function
268       
269        @throws TypeError if the inputs do match what the function accepts for
270        evaluation'''
271        raise NotImplementedError()
272
273
274class FunctionBase(Function):
275    FUNCTION_NS = "urn:oasis:names:tc:xacml:1.0:function:"
276    supportedIdentifiers = ()
277   
278    def __init__(self, 
279                 functionName, 
280                 functionId=None, 
281                 paramType=None,
282                 paramIsBag=False,
283                 numParams=0, 
284                 minParams=0,
285                 returnType='', 
286                 returnsBag=False):
287        '''
288        @param functionName the name of this function as used by the factory
289                            and any XACML policies
290        @param functionId an optional identifier that can be used by your
291                          code for convenience
292        @param paramTypes the type of each parameter, in order, required by
293                          this function, as used by the factory and any XACML
294                           documents
295        @param paramIsBag whether or not each parameter is actually a bag
296                          of values
297        @param returnType the type returned by this function, as used by
298                          the factory and any XACML documents
299        @param returnsBag whether or not this function returns a bag of values
300        '''
301         
302        self.functionName = functionName
303        self.functionId = functionId
304        self.returnType = None
305        self.returnsBag = False
306   
307        self.singleType = True;
308   
309        self.paramType = paramType
310        self.paramIsBag = paramIsBag
311        self.numParams = numParams
312        self.minParams = minParams
313       
314 
315    def _setFunctionName(self, functionName):
316          if functionName not in self.__class__.supportedIdentifiers:
317              functionList = ', '.join(self.__class__.supportedIdentifiers)
318              raise TypeError("Function name [%s] is not on of the recognised "
319                              "types: %s" % (functionName, functionList))
320          self._functionName = functionName
321         
322    def _getFunctionName(self):
323          return getattr(self, '_functionName', None)
324   
325    functionName = property(fset=_setFunctionName,
326                                    fget=_getFunctionName)
327         
328    def checkInputs(self, inputs):
329        '''Checks that the given inputs are of the right types, in the right
330        order, and are the right number for this function to evaluate.'''
331        raise NotImplementedError()
332           
333    def checkInputsNoBag(self, inputs):
334        '''Checks that the given inputs are of the right types, in the right
335        order, and are the right number for this function to evaluate.'''
336        raise NotImplementedError()
337 
338    def evaluate(self, inputs, context):
339        '''Evaluates the Function using the given inputs.'''
340        raise NotImplementedError()
341     
342    def evalArgs(self, params, context, args):
343        '''Evaluates each of the parameters, in order, filling in the argument
344        array with the resulting values. If any error occurs, this method
345        returns the error, otherwise null is returned, signalling that
346        evaluation was successful for all inputs, and the resulting argument
347        list can be used.
348       
349        @param params a list of Evaluatable objects representing the parameters
350        to evaluate
351        @param context the representation of the request
352        @param args an array as long as the params list that will, on return,
353        contain the AttributeValues generated from evaluating all parameters
354
355        @return None if no errors were encountered, otherwise
356        an EvaluationResult representing the error
357        '''
358        index = 0
359
360        for eval in params:
361            # get and evaluate the next parameter
362            result = eval.evaluate(context)
363
364            # If there was an error, pass it back...
365            if result.indeterminate():
366                return result
367
368            # ...otherwise save it and keep going
369            args[index] = result.getAttributeValue()
370            index += 1
371           
372        return None
373
374# TODO: Condition classes - minimal implementation until opportunity to fully
375# implement   
376class BagFunction(FunctionBase):
377    def __init__(self, *arg, **kw):
378        raise NotImplementedError()
379
380class SetFunction(FunctionBase):
381    def __init__(self, *arg, **kw):
382        raise NotImplementedError()
383       
384class ConditionBagFunction(BagFunction):
385    def __init__(self, *arg, **kw):
386        raise NotImplementedError()
387       
388class ConditionSetFunction(FunctionBase):
389    def __init__(self, *arg, **kw):
390        raise NotImplementedError()
391       
392class HigherOrderFunction(Function):
393    def __init__(self, *arg, **kw):
394        raise NotImplementedError()
395
396# TODO: Function classes - minimal implementation until opportunity to fully
397# implement                                   
398class LogicalFunction(FunctionBase):
399
400    def __init__(self, *arg, **kw):
401        raise NotImplementedError()
402
403class NOfFunction(FunctionBase):
404   
405    def __init__(self, *arg, **kw):
406        raise NotImplementedError()
407       
408class NotFunction(FunctionBase):
409   
410    def __init__(self, *arg, **kw):
411        raise NotImplementedError()
412       
413class ComparisonFunction(FunctionBase):
414   
415    def __init__(self, *arg, **kw):
416        raise NotImplementedError()
417
418class MatchFunction(FunctionBase):
419    NAME_REGEXP_STRING_MATCH = FunctionBase.FUNCTION_NS + "regexp-string-match"
420    NAME_RFC822NAME_MATCH = FunctionBase.FUNCTION_NS + "rfc822Name-match"
421    NAME_X500NAME_MATCH = FunctionBase.FUNCTION_NS + "x500Name-match"     
422
423    supportedIdentifiers = (NAME_REGEXP_STRING_MATCH, 
424                            NAME_RFC822NAME_MATCH,
425                            NAME_X500NAME_MATCH)
426   
427    lut = {
428          NAME_REGEXP_STRING_MATCH: 'regexpStringMatch',
429          NAME_RFC822NAME_MATCH:    'rfc822NameMatch',
430          NAME_X500NAME_MATCH:      'x500NameMatch'
431    }
432   
433    def __init__(self, functionName, **kw):
434          super(MatchFunction, self).__init__(functionName, **kw)
435
436    def regexpStringMatch(self, regex, val):
437          return re.match(regex, val) is not None
438   
439    def rfc822NameMatch(self, *inputs):
440        raise NotImplementedError()
441   
442    def x500NameMatch(self, *inputs):
443        raise NotImplementedError()
444   
445    def evaluate(self, inputs, context):
446          matchFunction = getattr(self, MatchFunction.lut[self.functionName])
447          match = matchFunction(self, *inputs)
448          if match:
449                return EvaluationResult(status=Status.STATUS_OK)
450
451
452class EqualFunction(FunctionBase):
453    supportedIdentifiers = (
454          FunctionBase.FUNCTION_NS + "anyURI-equal",
455          FunctionBase.FUNCTION_NS + "base64Binary-equal",
456          FunctionBase.FUNCTION_NS + "boolean-equal",
457          FunctionBase.FUNCTION_NS + "date-equal",
458          FunctionBase.FUNCTION_NS + "dateTime-equal",
459          FunctionBase.FUNCTION_NS + "dayTimeDuration-equal",
460          FunctionBase.FUNCTION_NS + "double-equal",
461          FunctionBase.FUNCTION_NS + "hexBinary-equal",
462          FunctionBase.FUNCTION_NS + "integer-equal",
463          FunctionBase.FUNCTION_NS + "rfc822Name-equal",
464          FunctionBase.FUNCTION_NS + "string-equal",
465          FunctionBase.FUNCTION_NS + "time-equal",
466          FunctionBase.FUNCTION_NS + "x500Name-equal",
467          FunctionBase.FUNCTION_NS + "yearMonthDuration-equal"
468    )
469
470    (NAME_ANYURI_EQUAL,
471    NAME_BASE64BINARY_EQUAL,
472    NAME_BOOLEAN_EQUAL,
473    NAME_DATE_EQUAL,
474    NAME_DATETIME_EQUAL,
475    NAME_DAYTIME_DURATION_EQUAL,
476    NAME_DOUBLE_EQUAL,
477    NAME_HEXBINARY_EQUAL,
478    NAME_INTEGER_EQUAL,
479    NAME_RFC822NAME_EQUAL,
480    NAME_STRING_EQUAL,
481    NAME_TIME_EQUAL,
482    NAME_X500NAME_EQUAL,
483    NAME_YEARMONTH_DURATION_EQUAL) = supportedIdentifiers
484
485    lut = {
486          NAME_STRING_EQUAL: 'stringEqual'
487    }
488   
489    _attrClasses = (
490        AnyURIAttribute,
491        Base64BinaryAttribute,
492        BooleanAttribute,
493        DateAttribute,
494        DateTimeAttribute,
495        DayTimeDurationEqual,
496        DoubleAttribute,
497        HexBinaryAttribute,
498        IntegerAttribute,
499        RFC822NameAttribute,
500        StringAttribute,
501        TimeAttribute,
502        X500NameAttribute,
503        YearMonthDurationAttribute
504    )
505   
506    typeMap = dict([(i, j.identifier) for i,j in zip(supportedIdentifiers,
507                                                     _attrClasses)])
508   
509    def __init__(self, functionName, argumentType=None, **kw):
510        if kw.get('functionId') is None:
511            kw['functionId'] = functionName
512           
513        if kw.get('paramType') is None:
514            kw['paramType'] = EqualFunction._getArgumentType(functionName)
515           
516        super(EqualFunction, self).__init__(functionName, **kw)
517
518    def evaluate(self, inputs, evaluationCtx):
519        function = EqualFunction.lut.get(self.functionName)
520        if function is None:
521            if self.functionName in supportedIdentifiers:
522                raise NotImplementedError("No implementation is available for "
523                                          "%s" % self.functionName)           
524            else:
525                raise AttributeError('function name "%s" not recognised '
526                                     'for %s' % (self.functionName,
527                                                 self.__class__.__name__))
528                                 
529        return getattr(self, function)(inputs, evaluationCtx)
530   
531    def stringEqual(self, inputs, evaluationCtx):
532        result = self.evalArgs(inputs, context, argValues)
533        if result is not None:
534            return result
535         
536        return EvaluationResult(argValues[0] == argValues[1])
537   
538    @classmethod
539    def _getArgumentType(cls, functionName):
540        argumentType = cls.typeMap.get(functionName)
541        if argumentType is None:
542            if functionName in cls.supportedIdentifiers:
543                raise NotImplementedError('No implementation is currently '
544                                          'available for "%s"' % functionName)
545            else:
546                raise TypeError("Not a standard function: %s" % functionName)
547         
548        return argumentType
549
550class AddFunction(FunctionBase):
551   
552    def __init__(self, *arg, **kw):
553        raise NotImplementedError()
554           
555class SubtractFunction(FunctionBase):
556   
557    def __init__(self, *arg, **kw):
558        raise NotImplementedError()
559           
560class MultiplyFunction(FunctionBase):
561   
562    def __init__(self, *arg, **kw):
563        raise NotImplementedError()
564           
565class DivideFunction(FunctionBase):
566   
567    def __init__(self, *arg, **kw):
568        raise NotImplementedError()
569           
570class ModFunction(FunctionBase):
571   
572    def __init__(self, *arg, **kw):
573        raise NotImplementedError()
574
575class AbsFunction(FunctionBase):
576   
577    def __init__(self, *arg, **kw):
578        raise NotImplementedError()
579
580class RoundFunction(FunctionBase):
581   
582    def __init__(self, *arg, **kw):
583        raise NotImplementedError()
584
585class FloorFunction(FunctionBase):
586   
587    def __init__(self, *arg, **kw):
588        raise NotImplementedError()
589
590class DateMathFunction(FunctionBase):
591   
592    def __init__(self, *arg, **kw):
593        raise NotImplementedError()
594
595class GeneralBagFunction(BagFunction):
596   
597    def __init__(self, *arg, **kw):
598        raise NotImplementedError()
599
600class NumericConvertFunction(FunctionBase):
601   
602    def __init__(self, *arg, **kw):
603        raise NotImplementedError()
604
605class StringNormalizeFunction(FunctionBase):
606   
607    def __init__(self, *arg, **kw):
608        raise NotImplementedError()
609
610class GeneralSetFunction(SetFunction):
611   
612    def __init__(self, *arg, **kw):
613        raise NotImplementedError()
614   
615class MapFunction(Function):       
616    supportedIdentifiers = ()
617    NAME_MAP = FunctionBase.FUNCTION_NS + "map"
618   
619    def __init__(self, *arg, **kw):
620        raise NotImplementedError()
621
622    @classmethod
623    def getInstance(cls, root):
624        raise NotImplementedError()
625   
626class FunctionProxy():
627
628    def getInstance(self, root):
629        raise NotImplementedError()
630
631class MapFunctionProxy(FunctionProxy):
632
633    def getInstance(self, root):
634        return MapFunction.getInstance(root)
Note: See TracBrowser for help on using the repository browser.