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

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

Added new access control interface and functionality to OpenID Provider to enable a custom context object to be passed between login and logout calls.

Line 
1"""XACML cond module contains condition function classes and class factories
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.utils import UniqList
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 Evaluatable(object):
24    '''Generic interface that is implemented by all objects that can appear in
25    an ApplyType. This lets the evaluation code of Apply and
26    functions iterate through their members and evaluate them, working only
27    on the returned values or errors.'''
28   
29    def evaluate(self, context):
30        '''Evaluates the object using the given context, and either returns an
31        error or a resulting value.
32   
33        @param context the representation of the request
34        @return the result of evaluation'''
35        raise NotImplementedError()
36
37    def getType(self):
38        '''Get the type of this object.  This may be the data type of an
39        Attribute or the return type of an
40        AttributeDesignator, etc.
41   
42        @return the type of data represented by this object'''
43        raise NotImplementedError()
44
45    def evaluatesToBag(self):
46        '''Tells whether evaluation will return a bag or a single value.
47   
48        @return true if evaluation will return a bag, false otherwise'''
49        raise NotImplementedError()
50
51    def getChildren(self):
52        '''Returns all children, in order, of this element in the Condition
53        tree, or en empty set if this element has no children. In XACML 1.x,
54        only the ApplyType ever has children.
55   
56        @return a list of Evaluatables'''
57        raise NotImplementedError()
58
59    def encode(self, output, indenter=None):
60        '''Encodes this Evaluatable into its XML representation and
61        writes this encoding to the given OutputStream with
62        indentation.
63   
64        @param output a stream into which the XML-encoded data is written
65        @param indenter an object that creates indentation strings'''
66        raise NotImplementedError()
67
68
69class Apply(Evaluatable):
70    '''Represents the XACML ApplyType and ConditionType XML types.'''
71
72    def __init__(self, function, evals, bagFunction=None, isCondition=False):
73        '''Constructs an Apply object. Throws an
74        IllegalArgumentException if the given parameter list
75        isn't valid for the given function.
76       
77        @param function the Function to use in evaluating the elements in the
78        apply
79        @param evals the contents of the apply which will be the parameters
80        to the function, each of which is an Evaluatable
81        @param bagFunction the higher-order function to use
82        @param isCondition Rrue if this Apply is a Condition, False otherwise
83        '''
84   
85        # check that the given inputs work for the function
86        inputs = evals
87        if bagFunction is not None:
88            inputs = [bagFunction]
89            inputs += evals
90       
91        function.checkInputs(inputs)
92
93        # if everything checks out, then store the inputs
94        self.function = function
95        self.evals = tuple(evals)
96        self.bagFunction = bagFunction
97        self.isCondition = isCondition
98   
99   
100    @staticmethod
101    def getConditionInstance(root, xpathVersion):
102        '''Returns an instance of an Apply based on the given DOM
103        root node. This will actually return a special kind of
104        Apply, namely an XML ConditionType, which is the root
105        of the condition logic in a RuleType. A ConditionType is the same
106        as an ApplyType except that it must use a FunctionId that returns
107        a boolean value.
108       
109        @param root the DOM root of a ConditionType XML type
110        @param xpathVersion the XPath version to use in any selectors or XPath
111                            functions, or null if this is unspecified (ie, not
112                            supplied in the defaults section of the policy)
113       
114        '''
115        raise NotImplementedError()
116         
117    def getInstance(self, 
118                    root, 
119                    factory=None, 
120                    isCondition=False, 
121                    xpathVersion=None):
122        '''Returns an instance of Apply based on the given DOM root.
123       
124        @param root the DOM root of an ApplyType XML type
125        @param xpathVersion the XPath version to use in any selectors or XPath
126                            functions, or null if this is unspecified (ie, not
127                            supplied in the defaults section of the policy)'''
128       
129        raise NotImplementedError()
130   
131    @staticmethod
132    def getFunction(root, version, factory):
133        '''Helper method that tries to get a function instance'''
134        raise NotImplementedError()
135           
136    def getFunction(self):
137        '''Returns the Function used by this Apply.
138       
139        @return the Function'''
140        return function
141   
142    def getChildren(self):
143        '''Returns the List of children for this Apply.
144        The List contains Evaluatables. The list is
145        unmodifiable, and may be empty.
146       
147        @return a List of Evaluatables'''
148        return self.evals
149   
150    def getHigherOrderFunction(self):
151        '''Returns the higher order bag function used by this Apply
152        if it exists, or null if no higher order function is used.
153       
154        @return the higher order Function or null'''
155        return self.bagFunction
156   
157    def isCondition(self):
158        '''Returns whether or not this ApplyType is actually a ConditionType.
159       
160        @return whether or not this represents a ConditionType'''
161        return isCondition
162
163    def evaluate(self, context):
164        '''Evaluates the apply object using the given function. This will in
165        turn call evaluate on all the given parameters, some of which may be
166        other Apply objects.
167       
168        @param context the representation of the request
169       
170        @return the result of trying to evaluate this apply object'''
171        parameters = self.evals
172
173        # see if there is a higher-order function in here
174        if bagFunction != None:
175            # this is a special case, so we setup the parameters, starting
176            # with the function
177            parameters = [bagFunction]
178
179            # now we evaluate all the parameters, returning INDETERMINATE
180            # if that's what any of them return, and otherwise tracking
181            # all the AttributeValues that get returned
182            for eval in self.evals:
183                result = eval.evaluate(context)
184               
185                # in a higher-order case, if anything is INDETERMINATE, then
186                # we stop right away
187                if result.indeterminate():
188                    return result
189
190                parameters.add(result.getAttributeValue())
191           
192        # now we can call the base function
193        return function.evaluate(parameters, context)
194         
195    def getType(self):
196        '''Returns the type of attribute that this object will return on a call
197        to evaluate. In practice, this will always be the same as
198        the result of calling getReturnType on the function used
199        by this object.
200       
201        @return the type returned by evaluate'''
202        return self.function.getReturnType()
203     
204    def evaluatesToBag(self):
205        '''Returns whether or not the Function will return a bag
206        of values on evaluation.
207       
208        @return true if evaluation will return a bag of values, false otherwise
209        '''
210        return self.function.returnsBag()
211
212    def encode(self, output, indenter):
213        '''Encodes this Apply into its XML representation and
214        writes this encoding to the given OutputStream with
215        indentation.
216       
217        @param output a stream into which the XML-encoded data is written
218        @param indenter an object that creates indentation strings'''
219        raise NotImplementedError()
220       
221class Function(object):
222    '''Interface that all functions in the system must implement.'''
223 
224    def evaluate(self, inputs, context):
225        '''Evaluates the Function using the given inputs.
226        The List contains Evaluatables which are all
227        of the correct type if the Function has been created as
228        part of an Apply or TargetMatch, but which
229        may otherwise be invalid. Each parameter should be evaluated by the
230        Function, unless this is a higher-order function (in
231        which case the Apply has already evaluated the inputs
232        to check for any INDETERMINATE conditions), or the Function
233        doesn't need to evaluate all inputs to determine a result (as in the
234        case of the or function). The order of the List is
235        significant, so a Function should have a very good reason
236        if it wants to evaluate the inputs in a different order.
237        <p>
238        Note that if this is a higher-order function, like any-of, then
239        the first argument in the List will actually be a Function
240        object representing the function to apply to some bag. In this case,
241        the second and any subsequent entries in the list are
242        AttributeValue objects (no INDETERMINATE values are
243        allowed, so the function is not given the option of dealing with
244        attributes that cannot be resolved). A function needs to know if it's
245        a higher-order function, and therefore whether or not to look for
246        this case. Also, a higher-order function is responsible for checking
247        that the inputs that it will pass to the Function
248        provided as the first parameter are valid, ie. it must do a
249        checkInputs on its sub-function when
250        checkInputs is called on the higher-order function.
251       
252        @param inputs the List of inputs for the function
253        @param context the representation of the request
254       
255        @return a result containing the AttributeValue computed
256                when evaluating the function, or Status
257                specifying some error condition'''
258        raise NotImplementedError()
259
260
261    def getIdentifier(self):
262        '''Returns the identifier of this function as known by the factories.
263        In the case of the standard XACML functions, this will be one of the
264        URIs defined in the standard namespace. This function must always
265        return the complete namespace and identifier of this function.
266       
267        @return the function's identifier'''
268        raise NotImplementedError()
269
270    def getReturnType(self):
271        '''Provides the type of AttributeValue that this function
272        returns from evaluate in a successful evaluation.
273       
274        @return the type returned by this function
275        '''
276        raise NotImplementedError()
277 
278    def returnsBag(self):
279        '''Tells whether this function will return a bag of values or just a
280        single value.
281       
282        @return true if evaluation will return a bag, false otherwise'''
283        raise NotImplementedError()
284
285    def checkInputs(self, inputs):
286        '''Checks that the given inputs are of the right types, in the right
287        order, and are the right number for this function to evaluate. If
288        the function cannot accept the inputs for evaluation, an
289        IllegalArgumentException is thrown.
290       
291        @param inputs a list of Evaluatables, with the first argument being a
292        Function if this is a higher-order function
293       
294        @throws TypeError if the inputs do match what the function accepts for
295        evaluation
296        '''
297        raise NotImplementedError()
298
299    def checkInputsNoBag(self, inputs):
300        '''Checks that the given inputs are of the right types, in the right
301        order, and are the right number for this function to evaluate. If
302        the function cannot accept the inputs for evaluation, an
303        IllegalArgumentException is thrown. Unlike the other
304        checkInput method in this interface, this assumes that
305        the parameters will never provide bags of values. This is useful if
306        you're considering a target function which has a designator or
307        selector in its input list, but which passes the values from the
308        derived bags one at a time to the function, so the function doesn't
309        have to deal with the bags that the selector or designator
310        generates.
311       
312        @param inputs a list of Evaluatables, with the first argument being a
313        Function if this is a higher-order function
314       
315        @throws TypeError if the inputs do match what the function accepts for
316        evaluation'''
317        raise NotImplementedError()
318
319
320class FunctionBase(Function):
321    FUNCTION_NS = "urn:oasis:names:tc:xacml:1.0:function:"
322    supportedIdentifiers = ()
323   
324    def __init__(self, 
325                 functionName, 
326                 functionId=None, 
327                 paramType=None,
328                 paramIsBag=False,
329                 numParams=0, 
330                 minParams=0,
331                 returnType='', 
332                 returnsBag=False):
333        '''
334        @param functionName the name of this function as used by the factory
335                            and any XACML policies
336        @param functionId an optional identifier that can be used by your
337                          code for convenience
338        @param paramTypes the type of each parameter, in order, required by
339                          this function, as used by the factory and any XACML
340                           documents
341        @param paramIsBag whether or not each parameter is actually a bag
342                          of values
343        @param returnType the type returned by this function, as used by
344                          the factory and any XACML documents
345        @param returnsBag whether or not this function returns a bag of values
346        '''
347         
348        self.functionName = functionName
349        self.functionId = functionId
350        self.returnType = None
351        self.returnsBag = False
352   
353        self.singleType = True;
354   
355        self.paramType = paramType
356        self.paramIsBag = paramIsBag
357        self.numParams = numParams
358        self.minParams = minParams
359       
360 
361    def _setFunctionName(self, functionName):
362          if functionName not in self.__class__.supportedIdentifiers:
363              functionList = ', '.join(self.__class__.supportedIdentifiers)
364              raise TypeError("Function name [%s] is not on of the recognised "
365                              "types: %s" % (functionName, functionList))
366          self._functionName = functionName
367         
368    def _getFunctionName(self):
369          return getattr(self, '_functionName', None)
370   
371    functionName = property(fset=_setFunctionName,
372                                    fget=_getFunctionName)
373         
374    def checkInputs(self, inputs):
375        '''Checks that the given inputs are of the right types, in the right
376        order, and are the right number for this function to evaluate.'''
377        raise NotImplementedError()
378           
379    def checkInputsNoBag(self, inputs):
380        '''Checks that the given inputs are of the right types, in the right
381        order, and are the right number for this function to evaluate.'''
382        raise NotImplementedError()
383 
384    def evaluate(self, inputs, context):
385        '''Evaluates the Function using the given inputs.'''
386        raise NotImplementedError()
387     
388    def evalArgs(self, params, context, args):
389        '''Evaluates each of the parameters, in order, filling in the argument
390        array with the resulting values. If any error occurs, this method
391        returns the error, otherwise null is returned, signalling that
392        evaluation was successful for all inputs, and the resulting argument
393        list can be used.
394       
395        @param params a list of Evaluatable objects representing the parameters
396        to evaluate
397        @param context the representation of the request
398        @param args an array as long as the params list that will, on return,
399        contain the AttributeValues generated from evaluating all parameters
400
401        @return None if no errors were encountered, otherwise
402        an EvaluationResult representing the error
403        '''
404        index = 0
405
406        for eval in params:
407            # get and evaluate the next parameter
408            result = eval.evaluate(context)
409
410            # If there was an error, pass it back...
411            if result.indeterminate():
412                return result
413
414            # ...otherwise save it and keep going
415            args[index] = result.getAttributeValue()
416            index += 1
417           
418        return None
419
420# TODO: Condition classes - minimal implementation until opportunity to fully
421# implement   
422class BagFunction(FunctionBase):
423    def __init__(self, *arg, **kw):
424        raise NotImplementedError()
425
426class SetFunction(FunctionBase):
427    def __init__(self, *arg, **kw):
428        raise NotImplementedError()
429       
430class ConditionBagFunction(BagFunction):
431    def __init__(self, *arg, **kw):
432        raise NotImplementedError()
433       
434class ConditionSetFunction(FunctionBase):
435    def __init__(self, *arg, **kw):
436        raise NotImplementedError()
437       
438class HigherOrderFunction(Function):
439    def __init__(self, *arg, **kw):
440        raise NotImplementedError()
441
442# TODO: Function classes - minimal implementation until opportunity to fully
443# implement                                   
444class LogicalFunction(FunctionBase):
445
446    def __init__(self, *arg, **kw):
447        raise NotImplementedError()
448
449class NOfFunction(FunctionBase):
450   
451    def __init__(self, *arg, **kw):
452        raise NotImplementedError()
453       
454class NotFunction(FunctionBase):
455   
456    def __init__(self, *arg, **kw):
457        raise NotImplementedError()
458       
459class ComparisonFunction(FunctionBase):
460   
461    def __init__(self, *arg, **kw):
462        raise NotImplementedError()
463
464class MatchFunction(FunctionBase):
465    NAME_REGEXP_STRING_MATCH = FunctionBase.FUNCTION_NS + "regexp-string-match"
466    NAME_RFC822NAME_MATCH = FunctionBase.FUNCTION_NS + "rfc822Name-match"
467    NAME_X500NAME_MATCH = FunctionBase.FUNCTION_NS + "x500Name-match"     
468
469    supportedIdentifiers = (NAME_REGEXP_STRING_MATCH, 
470                            NAME_RFC822NAME_MATCH,
471                            NAME_X500NAME_MATCH)
472   
473    lut = {
474          NAME_REGEXP_STRING_MATCH: 'regexpStringMatch',
475          NAME_RFC822NAME_MATCH:    'rfc822NameMatch',
476          NAME_X500NAME_MATCH:      'x500NameMatch'
477    }
478   
479    def __init__(self, functionName, **kw):
480          super(MatchFunction, self).__init__(functionName, **kw)
481
482    def regexpStringMatch(self, regex, val):
483          return re.match(regex, val) is not None
484   
485    def rfc822NameMatch(self, *inputs):
486        raise NotImplementedError()
487   
488    def x500NameMatch(self, *inputs):
489        raise NotImplementedError()
490   
491    def evaluate(self, inputs, context):
492          matchFunction = getattr(self, MatchFunction.lut[self.functionName])
493          match = matchFunction(self, *inputs)
494          if match:
495                return EvaluationResult(status=Status.STATUS_OK)
496
497
498class EqualFunction(FunctionBase):
499    supportedIdentifiers = (
500          FunctionBase.FUNCTION_NS + "anyURI-equal",
501          FunctionBase.FUNCTION_NS + "base64Binary-equal",
502          FunctionBase.FUNCTION_NS + "boolean-equal",
503          FunctionBase.FUNCTION_NS + "date-equal",
504          FunctionBase.FUNCTION_NS + "dateTime-equal",
505          FunctionBase.FUNCTION_NS + "dayTimeDuration-equal",
506          FunctionBase.FUNCTION_NS + "double-equal",
507          FunctionBase.FUNCTION_NS + "hexBinary-equal",
508          FunctionBase.FUNCTION_NS + "integer-equal",
509          FunctionBase.FUNCTION_NS + "rfc822Name-equal",
510          FunctionBase.FUNCTION_NS + "string-equal",
511          FunctionBase.FUNCTION_NS + "time-equal",
512          FunctionBase.FUNCTION_NS + "x500Name-equal",
513          FunctionBase.FUNCTION_NS + "yearMonthDuration-equal"
514    )
515
516    (NAME_ANYURI_EQUAL,
517    NAME_BASE64BINARY_EQUAL,
518    NAME_BOOLEAN_EQUAL,
519    NAME_DATE_EQUAL,
520    NAME_DATETIME_EQUAL,
521    NAME_DAYTIME_DURATION_EQUAL,
522    NAME_DOUBLE_EQUAL,
523    NAME_HEXBINARY_EQUAL,
524    NAME_INTEGER_EQUAL,
525    NAME_RFC822NAME_EQUAL,
526    NAME_STRING_EQUAL,
527    NAME_TIME_EQUAL,
528    NAME_X500NAME_EQUAL,
529    NAME_YEARMONTH_DURATION_EQUAL) = supportedIdentifiers
530
531    lut = {
532          NAME_STRING_EQUAL: 'stringEqual'
533    }
534   
535    _attrClasses = (
536        AnyURIAttribute,
537        Base64BinaryAttribute,
538        BooleanAttribute,
539        DateAttribute,
540        DateTimeAttribute,
541        DayTimeDurationEqual,
542        DoubleAttribute,
543        HexBinaryAttribute,
544        IntegerAttribute,
545        RFC822NameAttribute,
546        StringAttribute,
547        TimeAttribute,
548        X500NameAttribute,
549        YearMonthDurationAttribute
550    )
551   
552    typeMap = dict([(i, j.identifier) for i,j in zip(supportedIdentifiers,
553                                                     _attrClasses)])
554   
555    def __init__(self, functionName, argumentType=None, **kw):
556        if kw.get('functionId') is None:
557            kw['functionId'] = 0
558           
559        if kw.get('paramType') is None:
560            kw['paramType'] = EqualFunction._getArgumentType(functionName)
561           
562        super(EqualFunction, self).__init__(functionName, **kw)
563
564    def evaluate(self, inputs, evaluationCtx):
565        function = EqualFunction.lut.get(self.functionName)
566        if function is None:
567            if self.functionName in supportedIdentifiers:
568                raise NotImplementedError("No implementation is available for "
569                                          "%s" % self.functionName)           
570            else:
571                raise AttributeError('function name "%s" not recognised '
572                                     'for %s' % (self.functionName,
573                                                 self.__class__.__name__))
574                                 
575        return getattr(self, function)(inputs, evaluationCtx)
576   
577    def stringEqual(self, inputs, evaluationCtx):
578        result = self.evalArgs(inputs, context, argValues)
579        if result is not None:
580            return result
581         
582        return EvaluationResult(argValues[0] == argValues[1])
583   
584    @classmethod
585    def _getArgumentType(cls, functionName):
586        argumentType = cls.typeMap.get(functionName)
587        if argumentType is None:
588            if functionName in cls.supportedIdentifiers:
589                raise NotImplementedError('No implementation is currently '
590                                          'available for "%s"' % functionName)
591            else:
592                raise TypeError("Not a standard function: %s" % functionName)
593         
594        return argumentType
595
596class AddFunction(FunctionBase):
597   
598    def __init__(self, *arg, **kw):
599        raise NotImplementedError()
600           
601class SubtractFunction(FunctionBase):
602   
603    def __init__(self, *arg, **kw):
604        raise NotImplementedError()
605           
606class MultiplyFunction(FunctionBase):
607   
608    def __init__(self, *arg, **kw):
609        raise NotImplementedError()
610           
611class DivideFunction(FunctionBase):
612   
613    def __init__(self, *arg, **kw):
614        raise NotImplementedError()
615           
616class ModFunction(FunctionBase):
617   
618    def __init__(self, *arg, **kw):
619        raise NotImplementedError()
620
621class AbsFunction(FunctionBase):
622   
623    def __init__(self, *arg, **kw):
624        raise NotImplementedError()
625
626class RoundFunction(FunctionBase):
627   
628    def __init__(self, *arg, **kw):
629        raise NotImplementedError()
630
631class FloorFunction(FunctionBase):
632   
633    def __init__(self, *arg, **kw):
634        raise NotImplementedError()
635
636class DateMathFunction(FunctionBase):
637   
638    def __init__(self, *arg, **kw):
639        raise NotImplementedError()
640
641class GeneralBagFunction(BagFunction):
642   
643    def __init__(self, *arg, **kw):
644        raise NotImplementedError()
645
646class NumericConvertFunction(FunctionBase):
647   
648    def __init__(self, *arg, **kw):
649        raise NotImplementedError()
650
651class StringNormalizeFunction(FunctionBase):
652   
653    def __init__(self, *arg, **kw):
654        raise NotImplementedError()
655
656class GeneralSetFunction(SetFunction):
657   
658    def __init__(self, *arg, **kw):
659        raise NotImplementedError()
660   
661class MapFunction(Function):       
662    supportedIdentifiers = ()
663    NAME_MAP = FunctionBase.FUNCTION_NS + "map"
664   
665    def __init__(self, *arg, **kw):
666        raise NotImplementedError()
667
668    @classmethod
669    def getInstance(cls, root):
670        raise NotImplementedError()
671   
672class FunctionProxy():
673
674    def getInstance(self, root):
675        raise NotImplementedError()
676
677class MapFunctionProxy(FunctionProxy):
678
679    def getInstance(self, root):
680        return MapFunction.getInstance(root)
681
682
683class UnknownIdentifierException(Exception):
684    pass
685
686class FunctionTypeException(Exception):
687    pass
688
689class ParsingException(Exception):
690    pass
691
692
693class FunctionFactoryProxy(object):
694    '''A simple proxy interface used to install new <FunctionFactory>s.
695    The three kinds of factory (Target, Condition, and General) are tied
696    together in this interface because implementors writing new factories
697    should always implement all three types and provide them together'''
698   
699    @classmethod
700    def getTargetFactory(cls):
701        raise NotImplementedError()
702
703    @classmethod
704    def getConditionFactory(cls):
705        raise NotImplementedError()
706
707    @classmethod
708    def getGeneralFactory(cls):
709        raise NotImplementedError()
710
711
712class FunctionFactory(object):
713    '''Factory used to create all functions. There are three kinds of factories:
714    general, condition, and target. These provide functions that can be used
715    anywhere, only in a condition's root and only in a target (respectively).
716   
717    Note that all functions, except for abstract functions, are singletons, so
718    any instance that is added to a factory will be the same one returned
719    from the create methods. This is done because most functions don't have
720    state, so there is no need to have more than one, or to spend the time
721    creating multiple instances that all do the same thing.'''
722
723    class defaultFactoryProxy(FunctionFactoryProxy):
724        @classmethod
725        def getTargetFactory(cls):
726            return StandardFunctionFactory.getTargetFactory()
727   
728        @classmethod
729        def getConditionFactory(cls):
730            return StandardFunctionFactory.getConditionFactory()
731   
732        @classmethod
733        def getGeneralFactory(cls):
734            return StandardFunctionFactory.getGeneralFactory()                 
735           
736    @classmethod
737    def getTargetInstance(cls):
738        '''Returns the default FunctionFactory that will only provide those
739        functions that are usable in Target matching.
740       
741        @return a FunctionFactory for target functions'''
742        return cls.defaultFactoryProxy.getTargetFactory()
743       
744    @classmethod 
745    def getConditionInstance(cls): 
746        '''Returns the default FunctionFactory that provides access to all the
747        functions. These Functions are a superset of the Condition functions.
748       
749        @return a FunctionFactory for all functions
750        '''
751        return cls.defaultFactoryProxy.getConditionFactory()
752   
753    @classmethod
754    def getGeneralInstance(cls): 
755        '''Sets the default factory. Note that this is just a place holder for
756        now, and will be replaced with a more useful mechanism soon.'''
757        return cls.defaultFactoryProxy.getGeneralFactory()
758   
759   
760    def addFunction(self, function):
761        '''Adds the function to the factory. Most functions have no state, so
762        the singleton model used here is typically desirable. The factory will
763        not enforce the requirement that a Target or Condition matching
764        function must be boolean.
765       
766        @param function the Function to add to the factory
767        '''
768        raise NotImplementedError()
769       
770    def addAbstractFunction(self, functionProxy, identity):
771        '''Adds the abstract function proxy to the factory. This is used for
772        those functions which have state, or change behaviour (for instance
773        the standard map function, which changes its return type based on
774        how it is used).
775       
776        @param proxy the FunctionProxy to add to the factory
777        @param identity the function's identifier
778        '''
779        raise NotImplementedError()       
780   
781    def getSupportedFunctions(self):
782        '''Returns the function identifiers supported by this factory.
783       
784        @return a Set of Strings'''
785        raise NotImplementedError()
786
787    def createFunction(self, identity):
788        '''Tries to get an instance of the specified function.
789       
790        @param identity the name of the function
791        '''       
792        raise NotImplementedError()
793   
794    def createAbstractFunction(self, identity, root):
795        '''Tries to get an instance of the specified abstract function.
796       
797        @param identity the name of the function
798        @param root the DOM root containing info used to create the function
799        '''
800        raise NotImplementedError()
801
802
803class BasicFunctionFactoryProxy(FunctionFactoryProxy):
804    '''A simple utility class that manages triples of function factories.'''
805   
806    # the triple of factories
807    targetFactory = None
808    conditionFactory = None
809    generalFactory = None
810
811    def __init__(targetFactory, conditionFactory, generalFactory): 
812        '''Creates a new proxy.
813       
814        @param targetFactory the target factory provided by this proxy
815        @param conditionFactory the target condition provided by this proxy
816        @param generalFactory the general factory provided by this proxy
817        '''
818        self.targetFactory = targetFactory
819        self.conditionFactory = conditionFactory
820        self.generalFactory = generalFactory
821   
822    def getTargetFactory():
823        return targetFactory
824
825    def getConditionFactory():
826        return conditionFactory
827
828    def getGeneralFactory():
829        return generalFactory
830   
831
832class BaseFunctionFactory(FunctionFactory):
833    '''This is a basic implementation of <code>FunctionFactory</code>. It
834    implements the insertion and retrieval methods, but it doesn't actually
835    setup the factory with any functions. It also assumes a certain model
836    with regard to the different kinds of functions (Target, Condition, and
837    General). For this reason, you may want to re-use this class, or you
838    may want to extend FunctionFactory directly, if you're writing a new
839    factory implementation.
840   
841    Note that while this class is thread-safe on all creation methods, it
842    is not safe to add support for a new function while creating an instance
843    of a function. This follows from the assumption that most people will
844    initialize these factories up-front, and then start processing without
845    ever modifying the factories. If you need these mutual operations to
846    be thread-safe, then you should write a wrapper class that implements
847    the right synchronization.
848    '''
849   
850    def __init__(self, 
851                 superset=None, 
852                 supportedFunctions=[],
853                 supportedAbstractFunctions={}):
854        '''Sets a "superset factory". This is useful since
855        the different function factories (Target, Condition, and General)
856        have a superset relationship (Condition functions are a superset
857        of Target functions, etc.). Adding a function to this factory will
858        automatically add the same function to the superset factory.
859
860        Constructor that defines the initial functions supported by this
861        factory but doesn't use a superset factory.
862
863        Constructor that defines the initial functions supported by this
864        factory but doesn't use a superset factory.
865
866        Constructor that defines the initial functions supported by this
867        factory and uses a superset factory. Note that the functions
868        supplied here are not propagated up to the superset factory, so
869        you must either make sure the superset factory is correctly
870        initialized or use BaseFunctionFactory(FunctionFactory)
871        and then manually add each function.
872       
873        @param supportedFunctions a Set of Functions
874        @param supportedAbstractFunctions a mapping from URI to
875                                          FunctionProxy
876       
877        @param supportedFunctions a Set of Functions
878        @param supportedAbstractFunctions a mapping from URI to FunctionProxy
879       
880        @param superset the superset factory or None'''
881       
882        # the backing maps for the Function objects
883        self.functionMap = {}
884   
885        # the superset factory chained to this factory
886        self.superset = superset
887     
888        for function in supportedFunctions:
889            if function.functionId not in self.functionMap:
890                self.functionMap[function.functionId] = function
891       
892        for id in supportedAbstractFunctions.keys():
893            proxy = supportedAbstractFunctions.get(id)
894            self.functionMap[id] = proxy
895 
896    def addFunction(self, function):
897        '''Adds the function to the factory. Most functions have no state, so
898        the singleton model used here is typically desirable. The factory will
899        not enforce the requirement that a Target or Condition matching
900        function must be boolean.
901       
902        @param function the Function to add to the factory
903        @raise TypeError if the function's identifier is already used or if the
904        function is non-boolean (when this is a Target or Condition factory)
905        '''
906        id = function.functionId
907
908        # make sure this doesn't already exist
909        if id in self.functionMap:
910            raise TypeError("function %s already exists" % id)
911
912        # add to the superset factory
913        if self.superset != None:
914            self.superset.addFunction(function)
915
916        # Add to this factory
917        self.functionMap[id] = function
918   
919       
920    def addAbstractFunction(self, proxy, id):
921        '''Adds the abstract function proxy to the factory. This is used for
922        those functions which have state, or change behaviour (for instance
923        the standard map function, which changes its return type based on
924        how it is used).
925       
926        @param proxy: the FunctionProxy to add to the factory
927        @param id: the function's identifier
928       
929        @raise TypeError if the function's identifier is already used'''
930
931        # make sure this doesn't already exist
932        if id in self.functionMap:
933            raise TypeError("function already exists")
934
935        # add to the superset factory
936        if self.superset != None:
937            self.superset.addAbstractFunction(proxy, id)
938
939        # finally, add to this factory
940        functionMap[id] = proxy
941   
942
943    def getSupportedFunctions(self): 
944        '''Returns the function identifiers supported by this factory.
945       
946        @return a list of strings'''
947   
948        functions = self.functionMap.keys()
949
950        if self.superset != None:
951            functions += self.superset.getSupportedFunctions()
952
953        return functions
954   
955
956    def createFunction(self, identity):
957        '''Tries to get an instance of the specified function.
958       
959        @param identity the name of the function
960       
961        @throws UnknownIdentifierException if the name isn't known
962        @throws FunctionTypeException if the name is known to map to an
963                                      abstract function, and should therefore
964                                      be created through createAbstractFunction
965        '''
966        entry = self.functionMap.get(identity)
967        if entry is not None:
968            if isinstance(entry, Function): 
969                return entry
970            else:
971                # this is actually a proxy, which means the other create
972                # method should have been called
973                raise FunctionTypeException("function is abstract")   
974        else:
975            # we couldn't find a match
976            raise UnknownIdentifierException("functions of type %s are not "
977                                             "supported by this factory" % 
978                                             identity)       
979   
980   
981    def createAbstractFunction(identity, root):
982        '''Tries to get an instance of the specified abstract function.
983       
984        @param identity the name of the function
985        @param root the DOM root containing info used to create the function
986        @param xpathVersion the version specified in the containing policy, or
987                            None if no version was specified
988       
989        @throws UnknownIdentifierException if the name isn't known
990        @throws FunctionTypeException if the name is known to map to a
991                                      concrete function, and should therefore
992                                      be created through createFunction
993        @throws ParsingException if the function can't be created with the
994                                 given inputs'''
995   
996        entry = self.functionMap.get(identity)
997        if entry is not None:
998            if isinstance(entry, FunctionProxy): 
999                try: 
1000                    return entry.getInstance(root)
1001               
1002                except Exception, e:
1003                    raise ParsingException("Couldn't create abstract function "
1004                                           "%s: %s" % identity, e)     
1005            else:
1006                # this is actually a concrete function, which means that
1007                # the other create method should have been called
1008                raise FunctionTypeException("function is concrete")
1009           
1010        else:
1011            raise UnknownIdentifierException("Abstract functions of type %s "
1012                                             "are not supported by this "
1013                                             "factory" % identity)
1014
1015getSupportedFunctions = lambda cls: [cls(i) for i in cls.supportedIdentifiers]
1016
1017class StandardFunctionFactory(BaseFunctionFactory):
1018    '''This factory supports the standard set of functions specified in XACML
1019    1.0 and 1.1. It is the default factory used by the system, and imposes
1020    a singleton pattern insuring that there is only ever one instance of
1021    this class.
1022    <p>
1023    Note that because this supports only the standard functions, this
1024    factory does not allow the addition of any other functions. If you call
1025    addFunction on an instance of this class, an exception
1026    will be thrown. If you need a standard factory that is modifiable,
1027    you can either create a new BaseFunctionFactory (or some
1028    other implementation of FunctionFactory) populated with
1029    the standard functions from getStandardFunctions or
1030    you can use getNewFactoryProxy to get a proxy containing
1031    a new, modifiable set of factories.'''
1032
1033
1034    # the three singleton instances
1035    targetFactory = None
1036    conditionFactory = None
1037    generalFactory = None
1038
1039    # the three function sets/maps that we use internally
1040    targetFunctions = None
1041    conditionFunctions = None
1042    generalFunctions = None
1043
1044    targetAbstractFunctions = None
1045    conditionAbstractFunctions = None
1046    generalAbstractFunctions = None
1047
1048    # the set/map used by each singleton factory instance
1049    supportedFunctions = None
1050    supportedAbstractFunctions = None
1051
1052   
1053    def __init__(self, supportedFunctions, supportedAbstractFunctions): 
1054        '''Creates a new StandardFunctionFactory, making sure that the default
1055        maps are initialized correctly. Standard factories can't be modified,
1056        so there is no notion of supersetting since that's only used for
1057        correctly propagating new functions.'''
1058        super(StandardFunctionFactory, self).__init__(
1059                        supportedFunctions=supportedFunctions, 
1060                        supportedAbstractFunctions=supportedAbstractFunctions)
1061
1062        self.supportedFunctions = supportedFunctions
1063        self.supportedAbstractFunctions = supportedAbstractFunctions
1064   
1065    @classmethod
1066    def _initTargetFunctions(cls): 
1067        '''Private initializer for the target functions. This is only ever
1068        called once.'''
1069        log.info("Initializing standard Target functions")
1070
1071        cls.targetFunctions = UniqList()
1072
1073        # add EqualFunction
1074        cls.targetFunctions.extend(getSupportedFunctions(EqualFunction))
1075
1076        # add LogicalFunction
1077        cls.targetFunctions.extend(getSupportedFunctions(LogicalFunction))
1078       
1079        # add NOfFunction
1080        cls.targetFunctions.extend(getSupportedFunctions(NOfFunction))
1081       
1082        # add NotFunction
1083        cls.targetFunctions.extend(getSupportedFunctions(NotFunction))
1084       
1085        # add ComparisonFunction
1086        cls.targetFunctions.extend(getSupportedFunctions(ComparisonFunction))
1087
1088        # add MatchFunction
1089        cls.targetFunctions.extend(getSupportedFunctions(MatchFunction))
1090
1091        cls.targetAbstractFunctions = {}
1092   
1093    @classmethod
1094    def _initConditionFunctions(cls): 
1095        '''Private initializer for the condition functions. This is only ever
1096        called once.'''
1097        log.info("Initializing standard Condition functions")
1098
1099        if cls.targetFunctions is None:
1100            self._initTargetFunctions()
1101
1102        cls.conditionFunctions = cls.targetFunctions.copy()
1103
1104        # add condition functions from BagFunction
1105        conditionFunctions.extend(getSupportedFunctions(ConditionBagFunction))
1106       
1107        # add condition functions from SetFunction
1108        conditionFunctions.extend(getSupportedFunctions(ConditionSetFunction))
1109       
1110        # add condition functions from HigherOrderFunction
1111        conditionFunctions.extend(getSupportedFunctions(HigherOrderFunction))
1112
1113        cls.conditionAbstractFunctions = cls.targetAbstractFunctions.copy()
1114   
1115    @classmethod
1116    def _initGeneralFunctions(cls):     
1117        '''Private initializer for the general functions. This is only ever
1118        called once.'''
1119   
1120        log.info("Initializing standard General functions")
1121
1122        if cls.conditionFunctions is None:
1123            self._initConditionFunctions()
1124
1125        cls.generalFunctions = cls.conditionFunctions.copy()
1126
1127        # add AddFunction
1128        cls.generalFunctions.extend(getSupportedFunctions(AddFunction))
1129           
1130        # add SubtractFunction
1131        cls.generalFunctions.extend(getSupportedFunctions(SubtractFunction))
1132           
1133        # add MultiplyFunction
1134        cls.generalFunctions.extend(getSupportedFunctions(MultiplyFunction))
1135           
1136        # add DivideFunction
1137        cls.generalFunctions.extend(getSupportedFunctions(DivideFunction))
1138           
1139        # add ModFunction
1140        cls.generalFunctions.extend(getSupportedFunctions(ModFunction))
1141       
1142        # add AbsFunction
1143        cls.generalFunctions.extend(getSupportedFunctions(AbsFunction))
1144           
1145        # add RoundFunction
1146        cls.generalFunctions.extend(getSupportedFunctions(RoundFunction))
1147           
1148        # add FloorFunction
1149        cls.generalFunctions.extend(getSupportedFunctions(FloorFunction))
1150       
1151        # add DateMathFunction
1152        cls.generalFunctions.extend(getSupportedFunctions(DateMathFunction))
1153           
1154        # add general functions from BagFunction
1155        cls.generalFunctions.extend(getSupportedFunctions(GeneralBagFunction))
1156           
1157        # add NumericConvertFunction
1158        cls.generalFunctions.extend(getSupportedFunctions(
1159                                                    NumericConvertFunction))
1160           
1161        # add StringNormalizeFunction
1162        cls.generalFunctions.extend(getSupportedFunctions(
1163                                                    StringNormalizeFunction))
1164       
1165        # add general functions from SetFunction
1166        cls.generalFunctions.extend(getSupportedFunctions(GeneralSetFunction))
1167           
1168        cls.generalAbstractFunctions = cls.conditionAbstractFunctions.copy()
1169
1170        # Add the map function's proxy
1171        cls.generalAbstractFunctions[MapFunction.NAME_MAP] = MapFunctionProxy()
1172   
1173    @classmethod 
1174    def getTargetFactory(cls): 
1175        '''Returns a FunctionFactory that will only provide those functions
1176        that are usable in Target matching. This method enforces a singleton
1177        model, meaning that this always returns the same instance, creating
1178        the factory if it hasn't been requested before. This is the default
1179        model used by the FunctionFactory, ensuring quick
1180        access to this factory.
1181       
1182        @return a FunctionFactory for target functions'''
1183        if cls.targetFactory is None: 
1184            if cls.targetFunctions is None:
1185                cls._initTargetFunctions()
1186               
1187            if cls.targetFactory is None:
1188                cls.targetFactory=cls(cls.targetFunctions,
1189                                      cls.targetAbstractFunctions)
1190       
1191        return cls.targetFactory
1192
1193   
1194    @classmethod
1195    def getConditionFactory(cls): 
1196        '''Returns a FuntionFactory that will only provide those functions that
1197        are usable in the root of the Condition. These Functions are a
1198        superset of the Target functions. This method enforces a singleton
1199        model, meaning that this always returns the same instance, creating
1200        the factory if it hasn't been requested before. This is the default
1201        model used by the FunctionFactory, ensuring quick
1202        access to this factory.
1203   
1204        @return a FunctionFactory for condition functions
1205        '''
1206        if cls.conditionFactory is None:
1207            if cls.conditionFunctions is None:
1208                cls._initConditionFunctions()
1209               
1210            if cls.conditionFactory is None:
1211                cls.conditionFactory = cls(cls.conditionFunctions,
1212                                           cls.conditionAbstractFunctions)       
1213
1214        return cls.conditionFactory
1215   
1216
1217    @classmethod
1218    def getGeneralFactory(cls): 
1219        '''Returns a FunctionFactory that provides access to all the functions.
1220        These Functions are a superset of the Condition functions. This method
1221        enforces a singleton model, meaning that this always returns the same
1222        instance, creating the factory if it hasn't been requested before.
1223        This is the default model used by the FunctionFactory,
1224        ensuring quick access to this factory.
1225       
1226        @return a FunctionFactory for all functions'''
1227   
1228        if cls.generalFactory is None:
1229            if cls.generalFunctions is None:
1230                cls._initGeneralFunctions()
1231               
1232                cls.generalFactory = cls(cls.generalFunctions,
1233                                         cls.generalAbstractFunctions)
1234               
1235        return cls.generalFactory
1236
1237
1238    def getStandardFunctions(self):
1239        '''Returns the set of functions that this standard factory supports.
1240       
1241        @return a Set of Functions'''
1242        return tuple(self.supportedFunctions.keys())
1243       
1244    def getStandardAbstractFunctions(self):
1245        '''Returns the set of abstract functions that this standard factory
1246        supports as a mapping of identifier to proxy.
1247       
1248        @return a Map mapping URIs to FunctionProxys'''
1249        return tuple(self.supportedAbstractFunctions.keys())
1250   
1251   
1252    @classmethod
1253    def getNewFactoryProxy(cls): 
1254        '''A convenience method that returns a proxy containing newly created
1255        instances of BaseFunctionFactorys that are correctly
1256        supersetted and contain the standard functions and abstract functions.
1257        These factories allow adding support for new functions.
1258       
1259        @return a new proxy containing new factories supporting the standard
1260        functions'''
1261       
1262        general = cls.getGeneralFactory()
1263           
1264        newGeneral=BaseFunctionFactory(general.getStandardFunctions(),
1265                                       general.getStandardAbstractFunctions())
1266
1267        condition = cls.getConditionFactory()
1268       
1269        newCondition = BaseFunctionFactory(newGeneral,
1270                                    condition.getStandardFunctions(),
1271                                    condition.getStandardAbstractFunctions())
1272
1273        target = cls.getTargetFactory()
1274        newTarget = BaseFunctionFactory(newCondition,
1275                                    target.getStandardFunctions(),
1276                                    target.getStandardAbstractFunctions())
1277
1278        return BasicFunctionFactoryProxy(newTarget, newCondition, newGeneral)
1279   
1280    def addFunction(self, function):
1281        '''Always throws an exception, since support for new functions may not
1282        be added to a standard factory.
1283       
1284        @param function the Function to add to the factory       
1285        @raise NotImplementedError'''
1286   
1287        raise NotImplementedError("a standard factory cannot support new "
1288                                  "functions")
1289   
1290   
1291    def addAbstractFunction(self, proxy, identity):
1292        '''Always throws an exception, since support for new functions may not
1293        be added to a standard factory.
1294       
1295        @param proxy the FunctionProxy to add to the factory
1296        @param identity the function's identifier
1297       
1298        @raise NotImplementedError always'''
1299        raise NotImplementedError("a standard factory cannot support new "
1300                                  "functions")
Note: See TracBrowser for help on using the repository browser.