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

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

Re-release as rc1

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