source: TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/authz/xacml/cond/__init__.py @ 6557

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

Fix for ApacheSSLAuthnMiddleware - use comma separated list for accepted DNs. This enables DNs with fields containing spaces to be correctly parsed.

Line 
1"""XACML cond module contains condition function classes
2
3NERC DataGrid Project
4
5This code is adapted from the Sun Java XACML implementation ...
6
7Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
8
9Redistribution and use in source and binary forms, with or without
10modification, are permitted provided that the following conditions are met:
11
12  1. Redistribution of source code must retain the above copyright notice,
13     this list of conditions and the following disclaimer.
14
15  2. Redistribution in binary form must reproduce the above copyright
16     notice, this list of conditions and the following disclaimer in the
17     documentation and/or other materials provided with the distribution.
18
19Neither the name of Sun Microsystems, Inc. or the names of contributors may
20be used to endorse or promote products derived from this software without
21specific prior written permission.
22
23This software is provided "AS IS," without a warranty of any kind. ALL
24EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
25ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
26OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN")
27AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
28AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
29DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
30REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
31INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY
32OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE,
33EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
34
35You acknowledge that this software is not designed or intended for use in
36the design, construction, operation or maintenance of any nuclear facility.
37"""
38__author__ = "P J Kershaw"
39__date__ = "02/04/09"
40__copyright__ = "(C) 2009 Science and Technology Facilities Council"
41__contact__ = "Philip.Kershaw@stfc.ac.uk"
42__license__ = "BSD - see LICENSE file in top-level directory"
43__contact__ = "Philip.Kershaw@stfc.ac.uk"
44__revision__ = "$Id: $"
45import logging
46log = logging.getLogger(__name__)
47
48from ndg.security.common.utils.etree import QName
49from ndg.security.common.authz.xacml.exceptions import \
50    UnknownIdentifierException, ParsingException
51from ndg.security.common.authz.xacml.cond.eval import Evaluatable, \
52    EvaluationResult
53from ndg.security.common.authz.xacml.attr import AnyURIAttribute, \
54    Base64BinaryAttribute, BooleanAttribute, DateAttribute, DateTimeAttribute,\
55    DayTimeDurationAttribute, DoubleAttribute, HexBinaryAttribute, \
56    IntegerAttribute, RFC822NameAttribute, StringAttribute, TimeAttribute, \
57    X500NameAttribute, YearMonthDurationAttribute, AttributeFactory, \
58    AttributeDesignator
59
60
61class Apply(Evaluatable):
62    '''Represents the XACML ApplyType and ConditionType XML types.'''
63
64    def __init__(self, function, evals, bagFunction=None, isCondition=False):
65        '''Constructs an Apply object. Throws an
66        IllegalArgumentException if the given parameter list
67        isn't valid for the given function.
68       
69        @param function the Function to use in evaluating the elements in the
70        apply
71        @param evals the contents of the apply which will be the parameters
72        to the function, each of which is an Evaluatable
73        @param bagFunction the higher-order function to use
74        @param isCondition Rrue if this Apply is a Condition, False otherwise
75        '''
76   
77        # check that the given inputs work for the function
78        inputs = evals
79        if bagFunction is not None:
80            inputs = [bagFunction]
81            inputs += evals
82       
83        function.checkInputs(inputs)
84
85        # if everything checks out, then store the inputs
86        self._function = function
87        self._evals = tuple(evals)
88        self.bagFunction = bagFunction
89        self.isCondition = isCondition
90   
91    @classmethod
92    def getConditionInstance(cls, root):
93        '''Returns an instance of an Apply based on the given DOM
94        root node. This will actually return a special kind of
95        Apply, namely an XML ConditionType, which is the root
96        of the condition logic in a RuleType. A ConditionType is the same
97        as an ApplyType except that it must use a FunctionId that returns
98        a boolean value.
99       
100        @param root the DOM root of a ConditionType XML type
101        '''
102        from ndg.security.common.authz.xacml.cond.factory import \
103            FunctionFactory
104        cls.__getInstance(root, FunctionFactory.getConditionInstance(), True)
105   
106    @classmethod
107    def getInstance(cls, root):
108        '''Returns an instance of Apply based on the given root.
109         
110        @param root: the ElementTree.Element root of a ConditionType XML type
111        @raise ParsingException: if this is not a valid ApplyType
112        '''
113        from ndg.security.common.authz.xacml.cond.factory import \
114            FunctionFactory
115        cls.__getInstance(root, FunctionFactory.getGeneralInstance(), True)
116         
117       
118    @classmethod
119    def __getInstance(cls, root, factory, isCondition):
120        '''This is a helper method that is called by the two getInstance
121        methods. It takes a factory so we know that we're getting the right
122        kind of function.'''
123     
124        function = cls.__getFunction(root, factory)
125        bagFunction = None
126        evals = []
127       
128        attrFactory = AttributeFactory.getInstance()
129
130        for elem in root: 
131            name = QName.getLocalPart(elem.tag)
132
133            if name == "Apply":
134                evals.append(Apply.getInstance(elem))
135               
136            elif name == "AttributeValue":
137                try: 
138                    evals.append(attrFactory.createValue(elem))
139                   
140                except UnknownIdentifierException, e:
141                    raise ParsingException("Unknown DataType: %s" % e)
142               
143            elif name == "SubjectAttributeDesignator":
144                evals.append(AttributeDesignator.getInstance(elem,
145                                      AttributeDesignator.SUBJECT_TARGET))
146               
147            elif name =="ResourceAttributeDesignator":
148                evals.append(AttributeDesignator.getInstance(elem,
149                                      AttributeDesignator.RESOURCE_TARGET))
150               
151            elif name == "ActionAttributeDesignator": 
152                evals.append(AttributeDesignator.getInstance(elem,
153                                      AttributeDesignator.ACTION_TARGET))
154               
155            elif name == "EnvironmentAttributeDesignator":
156                evals.append(AttributeDesignator.getInstance(elem,
157                                      AttributeDesignator.ENVIRONMENT_TARGET))
158               
159            elif name == "AttributeSelector":
160                evals.append(AttributeSelector.getInstance(elem))
161               
162            elif name == "Function": 
163                # while the schema doesn't enforce this, it's illegal to
164                # have more than one FunctionType in a given ApplyType
165                if bagFunction != None:
166                    raise ParsingException("Too many FunctionTypes")
167
168                from ndg.security.common.authz.xacml.cond.factory import \
169                    FunctionFactory
170                bagFunction = cls.__getFunction(elem, 
171                                        FunctionFactory.getGeneralInstance())
172           
173        return Apply(function, evals, bagFunction, isCondition)
174
175
176    @classmethod
177    def __getFunction(cls, root, factory):
178        '''Helper method that tries to get a function instance'''
179
180        functionName = root.attrib["FunctionId"]
181        try:
182            # try to get an instance of the given function
183            return factory.createFunction(functionName)
184       
185        except UnknownIdentifierException, e:
186            raise ParsingException("Unknown FunctionId in Apply: %s" % e)
187       
188        except FunctionTypeException, e:
189            # try creating as an abstract function, using a general factory
190            try:
191                from ndg.security.common.authz.xacml.cond.factory import \
192                    FunctionFactory
193                functionFactory = FunctionFactory.getGeneralInstance()
194                return functionFactory.createAbstractFunction(functionName, 
195                                                              root)
196            except Exception, e:
197                # any exception at this point is a failure
198                raise ParsingException("failed to create abstract function %s "
199                                       ": %s" % (functionName, e)) 
200           
201    def getFunction(self):
202        '''Returns the Function used by this Apply.
203       
204        @return the Function'''
205        return self._function
206   
207    def getChildren(self):
208        '''Returns the List of children for this Apply.
209        The List contains Evaluatables. The list is
210        unmodifiable, and may be empty.
211       
212        @return a List of Evaluatables'''
213        return self._evals
214   
215    def getHigherOrderFunction(self):
216        '''Returns the higher order bag function used by this Apply
217        if it exists, or null if no higher order function is used.
218       
219        @return the higher order Function or null'''
220        return self.bagFunction
221   
222    def isCondition(self):
223        '''Returns whether or not this ApplyType is actually a ConditionType.
224       
225        @return whether or not this represents a ConditionType'''
226        return isCondition
227
228    def evaluate(self, context):
229        '''Evaluates the apply object using the given function. This will in
230        turn call evaluate on all the given parameters, some of which may be
231        other Apply objects.
232       
233        @param context the representation of the request
234       
235        @return the result of trying to evaluate this apply object'''
236        parameters = self.evals
237
238        # see if there is a higher-order function in here
239        if bagFunction != None:
240            # this is a special case, so we setup the parameters, starting
241            # with the function
242            parameters = [bagFunction]
243
244            # now we evaluate all the parameters, returning INDETERMINATE
245            # if that's what any of them return, and otherwise tracking
246            # all the AttributeValues that get returned
247            for eval in self.evals:
248                result = eval.evaluate(context)
249               
250                # in a higher-order case, if anything is INDETERMINATE, then
251                # we stop right away
252                if result.indeterminate():
253                    return result
254
255                parameters.add(result.getAttributeValue())
256           
257        # now we can call the base function
258        return function.evaluate(parameters, context)
259         
260    def getType(self):
261        '''Returns the type of attribute that this object will return on a call
262        to evaluate. In practice, this will always be the same as
263        the result of calling getReturnType on the function used
264        by this object.
265       
266        @return the type returned by evaluate'''
267        return self.function.getReturnType()
268     
269    def evaluatesToBag(self):
270        '''Returns whether or not the Function will return a bag
271        of values on evaluation.
272       
273        @return true if evaluation will return a bag of values, false otherwise
274        '''
275        return self.function.returnsBag()
276
277    def encode(self, output, indenter):
278        '''Encodes this Apply into its XML representation and
279        writes this encoding to the given OutputStream with
280        indentation.
281       
282        @param output a stream into which the XML-encoded data is written
283        @param indenter an object that creates indentation strings'''
284        raise NotImplementedError()
285       
286class Function(object):
287    '''Interface that all functions in the system must implement.'''
288 
289    def evaluate(self, inputs, context):
290        '''Evaluates the Function using the given inputs.
291        The List contains Evaluatables which are all
292        of the correct type if the Function has been created as
293        part of an Apply or TargetMatch, but which
294        may otherwise be invalid. Each parameter should be evaluated by the
295        Function, unless this is a higher-order function (in
296        which case the Apply has already evaluated the inputs
297        to check for any INDETERMINATE conditions), or the Function
298        doesn't need to evaluate all inputs to determine a result (as in the
299        case of the or function). The order of the List is
300        significant, so a Function should have a very good reason
301        if it wants to evaluate the inputs in a different order.
302        <p>
303        Note that if this is a higher-order function, like any-of, then
304        the first argument in the List will actually be a Function
305        object representing the function to apply to some bag. In this case,
306        the second and any subsequent entries in the list are
307        AttributeValue objects (no INDETERMINATE values are
308        allowed, so the function is not given the option of dealing with
309        attributes that cannot be resolved). A function needs to know if it's
310        a higher-order function, and therefore whether or not to look for
311        this case. Also, a higher-order function is responsible for checking
312        that the inputs that it will pass to the Function
313        provided as the first parameter are valid, ie. it must do a
314        checkInputs on its sub-function when
315        checkInputs is called on the higher-order function.
316       
317        @param inputs the List of inputs for the function
318        @param context the representation of the request
319       
320        @return a result containing the AttributeValue computed
321                when evaluating the function, or Status
322                specifying some error condition'''
323        raise NotImplementedError()
324
325
326    def getIdentifier(self):
327        '''Returns the identifier of this function as known by the factories.
328        In the case of the standard XACML functions, this will be one of the
329        URIs defined in the standard namespace. This function must always
330        return the complete namespace and identifier of this function.
331       
332        @return the function's identifier'''
333        raise NotImplementedError()
334
335    def getReturnType(self):
336        '''Provides the type of AttributeValue that this function
337        returns from evaluate in a successful evaluation.
338       
339        @return the type returned by this function
340        '''
341        raise NotImplementedError()
342 
343    def returnsBag(self):
344        '''Tells whether this function will return a bag of values or just a
345        single value.
346       
347        @return true if evaluation will return a bag, false otherwise'''
348        raise NotImplementedError()
349
350    def checkInputs(self, inputs):
351        '''Checks that the given inputs are of the right types, in the right
352        order, and are the right number for this function to evaluate. If
353        the function cannot accept the inputs for evaluation, an
354        IllegalArgumentException is thrown.
355       
356        @param inputs a list of Evaluatables, with the first argument being a
357        Function if this is a higher-order function
358       
359        @throws TypeError if the inputs do match what the function accepts for
360        evaluation
361        '''
362        raise NotImplementedError()
363
364    def checkInputsNoBag(self, inputs):
365        '''Checks that the given inputs are of the right types, in the right
366        order, and are the right number for this function to evaluate. If
367        the function cannot accept the inputs for evaluation, an
368        IllegalArgumentException is thrown. Unlike the other
369        checkInput method in this interface, this assumes that
370        the parameters will never provide bags of values. This is useful if
371        you're considering a target function which has a designator or
372        selector in its input list, but which passes the values from the
373        derived bags one at a time to the function, so the function doesn't
374        have to deal with the bags that the selector or designator
375        generates.
376       
377        @param inputs a list of Evaluatables, with the first argument being a
378        Function if this is a higher-order function
379       
380        @throws TypeError if the inputs do match what the function accepts for
381        evaluation'''
382        raise NotImplementedError()
383
384
385class FunctionBase(Function):
386    FUNCTION_NS = "urn:oasis:names:tc:xacml:1.0:function:"
387    supportedIdentifiers = ()
388   
389    def __init__(self, 
390                 functionName, 
391                 functionId=None, 
392                 paramType=None,
393                 paramIsBag=False,
394                 numParams=-1, 
395                 minParams=0,
396                 returnType='', 
397                 returnsBag=False):
398        '''
399        @param functionName: the name of this function as used by the factory
400                            and any XACML policies
401        @param functionId: an optional identifier that can be used by your
402                          code for convenience
403        @param paramType: the type of each parameter, in order, required by
404                          this function, as used by the factory and any XACML
405                           documents
406        @param paramIsBag: whether or not each parameter is actually a bag
407                          of values
408        @param numParams: the number of parameters required by this function,
409        or -1 if any number are allowed
410        @param minParams: the minimum number of parameters required if
411        numParams is -1
412        @param returnType: the type returned by this function, as used by
413                          the factory and any XACML documents
414        @param returnsBag: whether or not this function returns a bag of values
415        '''         
416        self.functionName = functionName
417        self.functionId = functionId
418        self.returnType = returnType
419        self.returnsBag = returnsBag
420   
421        self.paramType = paramType
422               
423        if isinstance(self.paramType, (list, tuple)):
424            if not self.paramType:
425                raise TypeError('"paramType" is set to an empty list or tuple')
426            self.singleType = False
427           
428            # Keep this test within the paramType is-a-list if-block otherwise
429            # it may fail checking the length of a bool
430            if len(paramIsBag) != len(self.paramType):
431                raise TypeError('"paramIsBag" and "paramType" inputs must '
432                                'have the same length')
433        else:
434            self.singleType = True
435           
436            # These only apply if the input parameters are all of a single type
437            self.numParams = numParams
438            self.minParams = minParams
439           
440        self.paramIsBag = paramIsBag
441       
442 
443    def _setFunctionName(self, functionName):
444          if functionName not in self.__class__.supportedIdentifiers:
445              functionList = ', '.join(self.__class__.supportedIdentifiers)
446              raise TypeError("Function name [%s] is not on of the recognised "
447                              "types: %s" % (functionName, functionList))
448          self._functionName = functionName
449         
450    def _getFunctionName(self):
451          return getattr(self, '_functionName', None)
452   
453    functionName = property(fset=_setFunctionName,
454                                    fget=_getFunctionName)
455         
456    def checkInputs(self, inputs):
457        '''Checks that the given inputs are of the right types, in the right
458        order, and are the right number for this function to evaluate.'''
459        raise NotImplementedError()
460           
461    def checkInputsNoBag(self, inputs):
462        '''Default handling of input checking. This does some simple checking
463        based on the type of constructor used. If you need anything more
464        complex, or if you used the simple constructor, then you must
465        override this method.
466   
467        @param inputs: a list of Evaluatable instances
468       
469        @raise TypeError: if the inputs won't work
470        '''
471        numInputs = len(inputs)
472       
473        if self.singleType:
474            # first check to see if we need bags
475            if sum(self.paramIsBag):
476                raise TypeError('"%s" needs bags on input' % self.functionName)
477
478            # now check on the length
479            if self.numParams != -1: 
480                if numInputs != self.numParams:
481                    raise TypeError('wrong number of args to "%s"' % 
482                                    self.functionName)
483            else: 
484                if numInputs < self.minParams:
485                    raise TypeError("not enough args to " % self.functionName)
486           
487
488            # finally check param list
489            for eval in inputs: 
490                if eval.getType().toString() != self.paramType:
491                    raise TypeError("Illegal parameter: input type is %s but "
492                                    "%s type is %s" % 
493                                    (eval.getType().toString(),
494                                     self.__class__.__name__,
495                                     self.paramType))
496           
497        else: 
498            # first, check the length of the inputs
499            if len(self.paramType) != numInputs:
500                raise TypeError('Wrong number of args to "%s"' % 
501                                self.functionName)
502
503            # Ensure everything is of the same, correct type
504            it = zip(inputs, self.paramType, self.paramIsBag)
505            for eval, paramType, paramIsBag in it:
506                if eval.type != paramType or paramIsBag:
507                    raise TypeError("Illegal parameter: input type is %s but "
508                                    "%s type is %s" % 
509                                    (eval.type,
510                                     self.__class__.__name__,
511                                     paramType))
512
513 
514    def evaluate(self, inputs, context):
515        '''Evaluates the Function using the given inputs.'''
516        raise NotImplementedError()
517     
518    def evalArgs(self, params, context, args):
519        '''Evaluates each of the parameters, in order, filling in the argument
520        array with the resulting values. If any error occurs, this method
521        returns the error, otherwise null is returned, signalling that
522        evaluation was successful for all inputs, and the resulting argument
523        list can be used.
524       
525        @param params a list of Evaluatable objects representing the parameters
526        to evaluate
527        @param context the representation of the request
528        @param args an array as long as the params list that will, on return,
529        contain the AttributeValues generated from evaluating all parameters
530
531        @return None if no errors were encountered, otherwise
532        an EvaluationResult representing the error
533        '''
534        index = 0
535
536        for eval in params:
537            # get and evaluate the next parameter
538            result = eval.evaluate(context)
539
540            # If there was an error, pass it back...
541            if result.indeterminate():
542                return result
543
544            # ...otherwise save it and keep going
545            args[index] = result.getAttributeValue()
546            index += 1
547           
548        return None
549
550# TODO: Condition classes - minimal implementation until opportunity to fully
551# implement   
552class BagFunction(FunctionBase):
553    def __init__(self, *arg, **kw):
554        raise NotImplementedError()
555
556class SetFunction(FunctionBase):
557    '''Represents all of the Set functions, though the actual implementations
558    are in two sub-classes specific to the condition and general set
559    functions.'''
560
561    # Base name for the type-intersection functions. To get the standard
562    # identifier for a given type, use FunctionBase.FUNCTION_NS
563    # + the datatype's base name (e.g., string) +
564    # NAME_BASE_INTERSECTION.
565    NAME_BASE_INTERSECTION = "-intersection"
566
567    # Base name for the type-at-least-one-member-of functions. To get the
568    # standard identifier for a given type, use
569    # FunctionBase.FUNCTION_NS + the datatype's base name (e.g., string) +
570    # NAME_BASE_AT_LEAST_ONE_MEMBER_OF.
571    NAME_BASE_AT_LEAST_ONE_MEMBER_OF = "-at-least-one-member-of"
572
573    # Base name for the type-union funtions. To get the standard
574    # identifier for a given type, use FunctionBase.FUNCTION_NS
575    # + the datatype's base name (e.g., string) + NAME_BASE_UNION.
576    NAME_BASE_UNION = "-union"
577
578    # Base name for the type-subset funtions. To get the standard
579    # identifier for a given type, use FunctionBase.FUNCTION_NS
580    # + the datatype's base name (e.g., string) + NAME_BASE_SUBSET.
581    NAME_BASE_SUBSET = "-subset"
582
583    # Base name for the type-set-equals funtions. To get the standard
584    # identifier for a given type, use FunctionBase.FUNCTION_NS
585    # + the datatype's base name (e.g., string) + NAME_BASE_SET_EQUALS.
586    NAME_BASE_SET_EQUALS = "-set-equals"
587
588   
589    # A complete list of all the XACML datatypes supported by the Set
590    # functions
591    baseTypes = (
592        StringAttribute.identifier,
593        BooleanAttribute.identifier,
594        IntegerAttribute.identifier,
595        DoubleAttribute.identifier,
596        DateAttribute.identifier,
597        DateTimeAttribute.identifier,
598        TimeAttribute.identifier,
599        AnyURIAttribute.identifier,
600        HexBinaryAttribute.identifier,
601        Base64BinaryAttribute.identifier,
602        DayTimeDurationAttribute.identifier,
603        YearMonthDurationAttribute.identifier,
604        X500NameAttribute.identifier,
605        RFC822NameAttribute.identifier)
606   
607    # A complete list of all the XACML datatypes supported by the Set
608    # functions, using the "simple" form of the names (eg, string
609    # instead of http:#www.w3.org/2001/XMLSchema#string)
610    simpleTypes = (
611        "string", 
612        "boolean", 
613        "integer", 
614        "double", 
615        "date", 
616        "dateTime",
617        "time", 
618        "anyURI", 
619        "hexBinary", 
620        "base64Binary", 
621        "dayTimeDuration",
622        "yearMonthDuration", 
623        "x500Name", 
624        "rfc822Name")
625
626    # Define as lambda to avoid reference to classes that aren't defined yet
627    _getSupportedIdentifiers = lambda: \
628        ConditionSetFunction.supportedIdentifiers +\
629        GeneralSetFunction.supportedIdentifiers
630       
631    # All the function identifiers supported by this class.
632    supportedIdentifiers = property(fget=_getSupportedIdentifiers)
633
634   
635    def __init__(self, 
636                 functionName, 
637                 functionId, 
638                 argumentType, 
639                 returnType,
640                 returnsBag):
641        '''Constuctor used by the general and condition subclasses only.
642        If you need to create a new SetFunction instance you
643        should either use one of the getInstance methods or
644        construct one of the sub-classes directly.
645       
646        @param functionName the identitifer for the function
647        @param functionId an optional, internal numeric identifier
648        @param argumentType the datatype this function accepts
649        @param returnType the datatype this function returns
650        @param returnsBag whether this function returns bags
651        '''
652        super(SetFunction, self).__init__(functionName, 
653                                          functionId, 
654                                          argumentType, 
655                                          True, 
656                                          2, 
657                                          returnType,
658                                          returnsBag)
659   
660       
661    @classmethod
662    def getIntersectionInstance(cls, functionName, argumentType):
663        '''Creates a new instance of the intersection set function.
664        This should be used to create support for any new attribute types
665        and then the new SetFunction object should be added
666        to the factory (all set functions for the base types are already
667        installed in the factory).
668       
669        @param functionName the name of the function
670        @param argumentType the attribute type this function will work with
671       
672        @return a new SetFunction for the given type
673        '''
674        return GeneralSetFunction(functionName, argumentType,
675                                  cls.NAME_BASE_INTERSECTION)
676   
677    @classmethod
678    def getAtLeastOneInstance(cls, functionName, argumentType):
679        '''Creates a new instance of the at-least-one-member-of set function.
680        This should be used to create support for any new attribute types
681        and then the new SetFunction object should be added
682        to the factory (all set functions for the base types are already
683        installed in the factory).
684       
685        @param functionName the name of the function
686        @param argumentType the attribute type this function will work with
687       
688        @return a new SetFunction for the given type
689        '''
690        return ConditionSetFunction(functionName, argumentType,
691                                    cls.NAME_BASE_AT_LEAST_ONE_MEMBER_OF)
692   
693    @classmethod
694    def getUnionInstance(cls, functionName, argumentType):
695        '''Creates a new instance of the union set function.
696        This should be used to create support for any new attribute types
697        and then the new SetFunction object should be added
698        to the factory (all set functions for the base types are already
699        installed in the factory).
700       
701        @param functionName the name of the function
702        @param argumentType the attribute type this function will work with
703       
704        @return a new SetFunction for the given type
705        '''
706        return GeneralSetFunction(functionName, argumentType,
707                                  cls.NAME_BASE_UNION)
708   
709    def getSubsetInstance(cls, functionName, argumentType):
710        '''Creates a new instance of the subset set function.
711        This should be used to create support for any new attribute types
712        and then the new SetFunction object should be added
713        to the factory (all set functions for the base types are already
714        installed in the factory).
715       
716        @param functionName the name of the function
717        @param argumentType the attribute type this function will work with
718       
719        @return a new SetFunction for the given type
720        '''
721        return ConditionSetFunction(functionName, argumentType,
722                                    cls.NAME_BASE_SUBSET)
723   
724    def getSetEqualsInstance(cls, functionName, argumentType):
725        '''Creates a new instance of the equals set function.
726        This should be used to create support for any new attribute types
727        and then the new SetFunction object should be added
728        to the factory (all set functions for the base types are already
729        installed in the factory).
730       
731        @param functionName the name of the function
732        @param argumentType the attribute type this function will work with
733       
734        @return a new SetFunction for the given type
735        '''
736        return ConditionSetFunction(functionName, argumentType,
737                                    cls.NAME_BASE_SET_EQUALS)
738
739       
740class ConditionSetFunction(SetFunction):
741    '''Specific SetFunction class that supports all of the
742    condition set functions: type-at-least-one-member-of, type-subset, and
743    type-set-equals.'''
744   
745    # Private identifiers for the supported functions
746    (ID_BASE_AT_LEAST_ONE_MEMBER_OF,
747     ID_BASE_SUBSET,
748     ID_BASE_SET_EQUALS) = range(3)
749
750    # Mapping of function name to its associated id and parameter type
751    idMap = {}
752    typeMap = {}
753    for baseType, simpleType in zip(SetFunction.baseTypes, 
754                                    SetFunction.simpleTypes):
755        baseName = SetFunction.FUNCTION_NS + simpleType
756
757        idMap[baseName + SetFunction.NAME_BASE_AT_LEAST_ONE_MEMBER_OF] = \
758                  ID_BASE_AT_LEAST_ONE_MEMBER_OF
759        idMap[baseName + SetFunction.NAME_BASE_SUBSET] = ID_BASE_SUBSET
760        idMap[baseName + SetFunction.NAME_BASE_SET_EQUALS] = ID_BASE_SET_EQUALS
761
762        typeMap[baseName+SetFunction.NAME_BASE_AT_LEAST_ONE_MEMBER_OF]=baseType
763        typeMap[baseName + SetFunction.NAME_BASE_SUBSET] = baseType
764        typeMap[baseName + SetFunction.NAME_BASE_SET_EQUALS] = baseType
765       
766    del baseName
767   
768    # the actual supported ids
769    supportedIdentifiers = tuple(idMap.keys())
770
771    idMap.update({
772        SetFunction.NAME_BASE_AT_LEAST_ONE_MEMBER_OF:
773                                            ID_BASE_AT_LEAST_ONE_MEMBER_OF,
774        SetFunction.NAME_BASE_SUBSET: ID_BASE_SUBSET,
775        SetFunction.NAME_BASE_SET_EQUALS: ID_BASE_SET_EQUALS}
776    )
777   
778   
779    def __init__(self, functionName, dataType=None):
780        '''Constructor that is used to create one of the condition standard
781        set functions. The name supplied must be one of the standard XACML
782        functions supported by this class, including the full namespace,
783        otherwise an exception is thrown. Look in SetFunction
784        for details about the supported names.
785       
786        @param functionName the name of the function to create
787       
788        @throws IllegalArgumentException if the function is unknown
789        '''
790        if dataType is None:
791            dataType = ConditionSetFunction.typeMap[functionName]
792           
793        super(ConditionSetFunction, self).__init__(functionName, 
794                                ConditionSetFunction.idMap[functionName], 
795                                dataType,
796                                BooleanAttribute.identifier, 
797                                False)
798
799   
800    def evaluate(self, inputs, context):
801        '''Evaluates the function, using the specified parameters.
802       
803        @param inputs a list of Evaluatable objects representing the arguments
804        passed to the function
805        @param context an EvaluationCtx so that the Evaluatable objects can be
806        evaluated
807        @return an EvaluationResult representing the function's result
808        '''
809
810        # Evaluate the arguments
811        argValues = AttributeValue[len(inputs)]
812        evalResult = self.evalArgs(inputs, context, argValues)
813        if evalResult is not None:
814            return evalResult
815
816        # Setup the two bags we'll be using
817        bags = argValues[:1]
818
819        result = None
820       
821        if self.functionId == \
822            ConditionSetFunction.ID_BASE_AT_LEAST_ONE_MEMBER_OF:
823           
824            #-at-least-one-member-of takes two bags of the same type and
825            # returns a boolean
826   
827            # true if at least one element in the first argument is in the
828            # second argument (using the-is-in semantics)
829
830            result = BooleanAttribute.getFalseInstance()
831            for it in bags[0]: 
832                if it in bags[1]: 
833                    result = BooleanAttribute.getTrueInstance()
834                    break
835               
836        elif self.functionId == ConditionSetFunction.ID_BASE_SUBSET:
837            #-set-equals takes two bags of the same type and returns
838            # a boolean
839           
840            # returns true if the first argument is a subset of the second
841            # argument (ie, all the elements in the first bag appear in
842            # the second bag) ... ignore all duplicate values in both
843            # input bags
844
845            subset = bags[1].containsAll(bags[0])
846            result = BooleanAttribute.getInstance(subset)
847
848        elif self.functionId == ConditionSetFunction.ID_BASE_SET_EQUALS:
849            #-set-equals takes two bags of the same type and returns
850            # a boolean
851
852            # returns true if the two inputs contain the same elements
853            # discounting any duplicates in either input ... this is the same
854            # as applying the and function on the subset function with
855            # the two inputs, and then the two inputs reversed (ie, are the
856            # two inputs subsets of each other)
857
858            equals = bags[1].containsAll(bags[0] and \
859                              bags[0].containsAll(bags[1]))
860            result = BooleanAttribute.getInstance(equals)
861       
862        return EvaluationResult(result) 
863   
864       
865class GeneralSetFunction(SetFunction):
866    supportedIdentifiers = ()
867    def __init__(self, *arg, **kw):
868        raise NotImplementedError()
869
870class ConditionBagFunction(BagFunction):
871    def __init__(self, *arg, **kw):
872        raise NotImplementedError()
873       
874class HigherOrderFunction(Function):
875    supportedIdentifiers = ()
876    def __init__(self, *arg, **kw):
877        raise NotImplementedError()
878
879# TODO: Function classes - minimal implementation until opportunity to fully
880# implement                                   
881class LogicalFunction(FunctionBase):
882
883    def __init__(self, *arg, **kw):
884        raise NotImplementedError()
885
886class NOfFunction(FunctionBase):
887   
888    def __init__(self, *arg, **kw):
889        raise NotImplementedError()
890       
891class NotFunction(FunctionBase):
892   
893    def __init__(self, *arg, **kw):
894        raise NotImplementedError()
895       
896class ComparisonFunction(FunctionBase):
897   
898    def __init__(self, *arg, **kw):
899        raise NotImplementedError()
900
901class MatchFunction(FunctionBase):
902    NAME_REGEXP_STRING_MATCH = FunctionBase.FUNCTION_NS + "regexp-string-match"
903    NAME_RFC822NAME_MATCH = FunctionBase.FUNCTION_NS + "rfc822Name-match"
904    NAME_X500NAME_MATCH = FunctionBase.FUNCTION_NS + "x500Name-match"     
905   
906    supportedIdentifiers = (
907        NAME_REGEXP_STRING_MATCH, 
908        NAME_RFC822NAME_MATCH,
909        NAME_X500NAME_MATCH)
910   
911    functionIds = range(3)
912    ID_REGEXP_STRING_MATCH, ID_X500NAME_MATCH, ID_RFC822NAME_MATCH=functionIds
913    getId = dict(zip(supportedIdentifiers, functionIds))
914    argParams = (
915        (StringAttribute.identifier,)*2,
916        (X500NameAttribute.identifier,)*2,
917        (StringAttribute.identifier,
918         RFC822NameAttribute.identifier)
919    )
920    getArgumentTypes = dict(zip(supportedIdentifiers, argParams))
921   
922    bagParams = (False, False)
923   
924    lut = {
925          NAME_REGEXP_STRING_MATCH: 'regexpStringMatch',
926          NAME_RFC822NAME_MATCH:    'rfc822NameMatch',
927          NAME_X500NAME_MATCH:      'x500NameMatch'
928    }
929   
930    def __init__(self, functionName, **kw):
931          super(MatchFunction, self).__init__(functionName, 
932                     functionId=MatchFunction.getId[functionName], 
933                     paramType=MatchFunction.getArgumentTypes[functionName],
934                     paramIsBag=MatchFunction.bagParams,
935                     returnType=BooleanAttribute.identifier, 
936                     returnsBag=False)
937
938
939    def regexpStringMatch(self, regex, val):
940          return re.match(regex, val) is not None
941   
942    def rfc822NameMatch(self, *inputs):
943        raise NotImplementedError()
944   
945    def x500NameMatch(self, *inputs):
946        raise NotImplementedError()
947   
948    def evaluate(self, inputs, context):
949          matchFunction = getattr(self, MatchFunction.lut[self.functionName])
950          match = matchFunction(self, *inputs)
951          if match:
952                return EvaluationResult(status=Status.STATUS_OK)
953
954
955class EqualFunction(FunctionBase):
956    supportedIdentifiers = (
957          FunctionBase.FUNCTION_NS + "anyURI-equal",
958          FunctionBase.FUNCTION_NS + "base64Binary-equal",
959          FunctionBase.FUNCTION_NS + "boolean-equal",
960          FunctionBase.FUNCTION_NS + "date-equal",
961          FunctionBase.FUNCTION_NS + "dateTime-equal",
962          FunctionBase.FUNCTION_NS + "dayTimeDuration-equal",
963          FunctionBase.FUNCTION_NS + "double-equal",
964          FunctionBase.FUNCTION_NS + "hexBinary-equal",
965          FunctionBase.FUNCTION_NS + "integer-equal",
966          FunctionBase.FUNCTION_NS + "rfc822Name-equal",
967          FunctionBase.FUNCTION_NS + "string-equal",
968          FunctionBase.FUNCTION_NS + "time-equal",
969          FunctionBase.FUNCTION_NS + "x500Name-equal",
970          FunctionBase.FUNCTION_NS + "yearMonthDuration-equal"
971    )
972
973    (NAME_ANYURI_EQUAL,
974    NAME_BASE64BINARY_EQUAL,
975    NAME_BOOLEAN_EQUAL,
976    NAME_DATE_EQUAL,
977    NAME_DATETIME_EQUAL,
978    NAME_DAYTIME_DURATION_EQUAL,
979    NAME_DOUBLE_EQUAL,
980    NAME_HEXBINARY_EQUAL,
981    NAME_INTEGER_EQUAL,
982    NAME_RFC822NAME_EQUAL,
983    NAME_STRING_EQUAL,
984    NAME_TIME_EQUAL,
985    NAME_X500NAME_EQUAL,
986    NAME_YEARMONTH_DURATION_EQUAL) = supportedIdentifiers
987
988    lut = {
989          NAME_STRING_EQUAL: 'stringEqual'
990    }
991   
992    _attrClasses = (
993        AnyURIAttribute,
994        Base64BinaryAttribute,
995        BooleanAttribute,
996        DateAttribute,
997        DateTimeAttribute,
998        DayTimeDurationAttribute,
999        DoubleAttribute,
1000        HexBinaryAttribute,
1001        IntegerAttribute,
1002        RFC822NameAttribute,
1003        StringAttribute,
1004        TimeAttribute,
1005        X500NameAttribute,
1006        YearMonthDurationAttribute
1007    )
1008   
1009    typeMap = dict([(i, j.identifier) for i,j in zip(supportedIdentifiers,
1010                                                     _attrClasses)])
1011   
1012    def __init__(self, functionName, argumentType=None, **kw):
1013        if kw.get('functionId') is None:
1014            kw['functionId'] = functionName
1015           
1016        if kw.get('paramType') is None:
1017            kw['paramType'] = EqualFunction._getArgumentType(functionName)
1018           
1019        super(EqualFunction, self).__init__(functionName, **kw)
1020
1021    def evaluate(self, inputs, evaluationCtx):
1022        function = EqualFunction.lut.get(self.functionName)
1023        if function is None:
1024            if self.functionName in supportedIdentifiers:
1025                raise NotImplementedError("No implementation is available for "
1026                                          "%s" % self.functionName)           
1027            else:
1028                raise AttributeError('function name "%s" not recognised '
1029                                     'for %s' % (self.functionName,
1030                                                 self.__class__.__name__))
1031                                 
1032        return getattr(self, function)(inputs, evaluationCtx)
1033   
1034    def stringEqual(self, inputs, evaluationCtx):
1035        result = self.evalArgs(inputs, context, argValues)
1036        if result is not None:
1037            return result
1038         
1039        return EvaluationResult(argValues[0] == argValues[1])
1040   
1041    @classmethod
1042    def _getArgumentType(cls, functionName):
1043        argumentType = cls.typeMap.get(functionName)
1044        if argumentType is None:
1045            if functionName in cls.supportedIdentifiers:
1046                raise NotImplementedError('No implementation is currently '
1047                                          'available for "%s"' % functionName)
1048            else:
1049                raise TypeError("Not a standard function: %s" % functionName)
1050         
1051        return argumentType
1052
1053class AddFunction(FunctionBase):
1054   
1055    def __init__(self, *arg, **kw):
1056        raise NotImplementedError()
1057           
1058class SubtractFunction(FunctionBase):
1059   
1060    def __init__(self, *arg, **kw):
1061        raise NotImplementedError()
1062           
1063class MultiplyFunction(FunctionBase):
1064   
1065    def __init__(self, *arg, **kw):
1066        raise NotImplementedError()
1067           
1068class DivideFunction(FunctionBase):
1069   
1070    def __init__(self, *arg, **kw):
1071        raise NotImplementedError()
1072           
1073class ModFunction(FunctionBase):
1074   
1075    def __init__(self, *arg, **kw):
1076        raise NotImplementedError()
1077
1078class AbsFunction(FunctionBase):
1079   
1080    def __init__(self, *arg, **kw):
1081        raise NotImplementedError()
1082
1083class RoundFunction(FunctionBase):
1084   
1085    def __init__(self, *arg, **kw):
1086        raise NotImplementedError()
1087
1088class FloorFunction(FunctionBase):
1089   
1090    def __init__(self, *arg, **kw):
1091        raise NotImplementedError()
1092
1093class DateMathFunction(FunctionBase):
1094   
1095    def __init__(self, *arg, **kw):
1096        raise NotImplementedError()
1097
1098class GeneralBagFunction(BagFunction):
1099   
1100    def __init__(self, *arg, **kw):
1101        raise NotImplementedError()
1102
1103class NumericConvertFunction(FunctionBase):
1104   
1105    def __init__(self, *arg, **kw):
1106        raise NotImplementedError()
1107
1108class StringNormalizeFunction(FunctionBase):
1109   
1110    def __init__(self, *arg, **kw):
1111        raise NotImplementedError()
1112   
1113class MapFunction(Function):       
1114    supportedIdentifiers = ()
1115    NAME_MAP = FunctionBase.FUNCTION_NS + "map"
1116   
1117    def __init__(self, *arg, **kw):
1118        raise NotImplementedError()
1119
1120    @classmethod
1121    def getInstance(cls, root):
1122        raise NotImplementedError()
1123   
1124class FunctionProxy():
1125
1126    def getInstance(self, root):
1127        raise NotImplementedError()
1128
1129class MapFunctionProxy(FunctionProxy):
1130
1131    def getInstance(self, root):
1132        return MapFunction.getInstance(root)
Note: See TracBrowser for help on using the repository browser.