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

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

Added to code for PDP to parse policy doc

Line 
1"""XACML Policy Decision Point module
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "13/02/09"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__contact__ = "Philip.Kershaw@stfc.ac.uk"
9__license__ = "BSD - see LICENSE file in top-level directory"
10__contact__ = "Philip.Kershaw@stfc.ac.uk"
11__revision__ = "$Id$"
12
13import logging
14log = logging.getLogger(__name__)
15
16from ndg.security.common.authz.pdp import PDPInterface
17
18# For parsing: ElementTree helpers
19getNs = lambda elem: elem.tag.split('}')[0][1:]
20getLocalName = lambda elem: elem.tag.rsplit('}',1)[-1]
21
22class XacmlBase(object):
23    pass
24
25class Subject(XacmlBase):
26    '''XACML Subject designator'''
27    def __init__(self, attributes={}):
28        self.attributes = attributes
29
30class Resource(XacmlBase):
31    '''XACML Resource designator'''
32
33class Action(XacmlBase):
34    '''XACML Action designator'''
35
36class Environment(XacmlBase):
37    '''XACML Environment designator'''
38
39class PolicySet(XacmlBase):
40    def __init__(self):
41          self.policies = []
42          self.combiningAlg = None
43         
44class Policy(XacmlBase):
45
46    def __init__(self,
47                 id='',
48                 ruleCombiningAlg=None,
49                 description='',
50                 target=None,
51                 rules=[],
52                 obligations=[]):
53          self.id = id
54          self.description = description
55          self.rules = rules
56          self.ruleCombiningAlg = ruleCombiningAlg
57          self.obligations = obligations
58          self.target = target
59
60    def encode(self):
61        '''Encode the policy'''
62        raise NotImplemented()
63   
64    @classmethod
65    def getInstance(cls, elem):
66       
67        for elem in elem.getchildren():
68            localName = getLocalName(elem)
69            if localName == 'Description':
70                description = elem.text
71               
72            elif localName == 'Target':
73                target = Target.getInstance(elem)
74               
75            elif localName == 'Rule':
76                pass
77           
78        policy = cls(id=root.attrib['PolicyId'], 
79                     ruleCombiningAlg=root.attrib['RuleCombiningAlg'],
80                     description=description,
81                     target=target,
82                     rules=rules,
83                     obligations=obligations)
84        return policy
85
86class MatchResult(XacmlBase):
87    pass
88
89class Target(XacmlBase):
90    '''The target selects policies relevant to a request'''
91
92    def __init__(self, subjects=None, resources=None, actions=None):
93          self.subjects = subjects
94          self.resources = resources
95          self.actions = actions
96          self.rules = []
97
98    def Match(self, evaluationCtx):
99          return MatchResult()
100       
101    @classmethod
102    def getInstance(cls, root):
103        '''Parse a Target from a given XML ElementTree element
104        '''
105        subjects = None
106        resources = None
107        actions = None
108       
109        for elem in root.getchildren():
110            localName = getLocalName(elem)
111
112            if localName == "Subjects":
113                subjects = Target._getAttributes(elem, "Subject")
114               
115            elif name == "Resources":
116                resources = Target._getAttributes(elem, "Resource")
117               
118            elif name == "Actions":
119                actions = Target._getAttributes(elem, "Action")
120       
121        return cls()
122   
123    @staticmethod
124    def _getAttributes(root, prefix):
125        '''Helper method to get Target children elements'''
126        matches = []
127
128        for elem in root.getchildren():
129            localName = getLocalName(elem)
130
131            if localName == prefix:
132                matches += getMatches(elem, prefix)
133            elif name == "Any" + prefix:
134                return None
135
136        return matches
137   
138    @staticmethod
139    def _getMatches(root, prefix):
140
141        list = []
142
143        for elem in root.getchildren():
144            localName = getLocalName(elem)
145
146            if localName == prefix + "Match":
147                list += TargetMatch.getInstance(child, prefix)
148
149        return tuple(list)
150   
151   
152class AttributeDesignator(XacmlBase):
153    ACTION_TARGET, ENVIRONMENT_TARGET, RESOURCE_TARGET, SUBJECT_TARGET=range(4)
154
155    def __init__(self, target, type, id, mustBePresent=False, issuer=None):
156          self.target = target
157          self.type = type
158          self.id = id
159          self.mustBePresent = mustBePresent
160          self.issuer = issuer
161
162    def getInstance(self):
163        pass
164   
165class TargetMatch(XacmlBase):
166    '''Represents the SubjectMatch, ResourceMatch, or ActionMatch XML
167    types in XACML, depending on the value of the type field. This is the
168    part of the Target that actually evaluates whether the specified
169    attribute values in the Target match the corresponding attribute
170    values in the request context.
171    '''
172    types = range(3)
173    SUBJECT, RESOURCE, ACTION = types
174   
175    def __init__(self,
176                 type,
177                 function,
178                 eval,
179                 attributeValue):
180        '''Create a TargetMatch from components.
181         
182        @param type an integer indicating whether this class represents a
183        SubjectMatch, ResourceMatch, or ActionMatch
184        @param function the Function that represents the MatchId
185        @param eval the AttributeDesignator or AttributeSelector to be used to
186        select attributes from the request context
187        @param attrValue the AttributeValue to compare against
188        @raise TypeError if the input type isn't a valid value
189        '''
190        self.type = type
191        self.function = function
192        self.eval = eval
193        self.attrValue = attributeValue
194
195    def _getType(self):
196        return getattr(self, '_type', None)
197   
198    def _setType(self, type):
199        if type not in self.__class__.types:
200            raise TypeError('Type value "%d" not recognised, expecting one of '
201                            '%r types' % (type, self.__class__.types))
202        self._type = type
203       
204    type = property(fget=_getType, fset=_setType, 
205                    doc="the type of match for this target")
206   
207    @classmethod
208    def getInstance(cls, root, prefix, xpathVersion):
209        '''Creates a TargetMatch by parsing a node, using the
210        input prefix to determine whether this is a SubjectMatch,
211        ResourceMatch, or ActionMatch.
212     
213        @param root the node to parse for the TargetMatch
214        @param prefix a String indicating what type of TargetMatch
215        to instantiate (Subject, Resource, or Action)
216        @param xpathVersion the XPath version to use in any selectors, or
217        null if this is unspecified (ie, not supplied in
218        the defaults section of the policy)
219
220        @return a new TargetMatch constructed by parsing
221        '''
222
223        action = ["Subject", "Resource", "Action"].index(prefix)
224        if action not in self.__class__.types:
225            raise TypeError("Unknown TargetMatch type: %s" % prefix)
226
227        # function type
228        funcId = root.attrib["MatchId"]
229        factory = FunctionFactory.getTargetInstance()
230        try:
231            function = factory.createFunction(funcId)
232        except UnknownIdentifierException, e:
233            raise ParsingException("Unknown MatchId: %s" % e)
234       
235        except FunctionTypeException, e:
236            # try to create an abstract function
237            try:
238                function = factory.createAbstractFunction(funcId, root)
239            except Exception, e:
240                raise ParsingException("invalid abstract function: %s" % e)
241           
242        # Get the designator or selector being used, and the attribute
243        # value paired with it
244        for elem in root.getchildren():
245            localName = getLocalName(elem)
246
247            if name == prefix + "AttributeDesignator":
248                eval = AttributeDesignator.getInstance(node, type)
249               
250            elif name == "AttributeSelector":
251                eval = AttributeSelector.getInstance(node)
252               
253            elif name == "AttributeValue":
254                try:
255                    attrValue = attrFactory.createValue(node)
256                except UnknownIdentifierException, e:
257                    raise ParsingException("Unknown Attribute Type: %s" % e)
258
259        # finally, check that the inputs are valid for this function
260        inputs = [attrValue, eval]
261        function.checkInputsNoBag(inputs)
262       
263        return cls(type, function, eval, attributeValue)
264   
265
266    def match(self, context):
267        '''determines whether this <code>TargetMatch</code> matches
268        the input request (whether it is applicable)
269
270        @param context the representation of the request
271
272        @return the result of trying to match the TargetMatch and the request
273        '''
274       
275        result = eval.evaluate(context)
276       
277        if result.indeterminate():
278            # in this case, we don't ask the function for anything, and we
279            # simply return INDETERMINATE
280            return MatchResult(MatchResult.INDETERMINATE, result.getStatus())
281       
282
283        bag = result.getAttributeValue()
284
285        if not bag.isEmpty():
286           
287            # we got back a set of attributes, so we need to iterate through
288            # them, seeing if at least one matches
289            it = bag.iterator()
290            atLeastOneError = False
291            firstIndeterminateStatus = None
292
293            while it.hasNext():
294                inputs = []
295
296                inputs.add(attrValue)
297                inputs.add(it.next())
298
299                # do the evaluation
300                match = evaluateMatch(inputs, context)
301               
302                # we only need one match for this whole thing to match
303                if match.getResult() == MatchResult.MATCH:
304                    return match
305
306                # if it was INDETERMINATE, we want to remember for later
307                if match.getResult() == MatchResult.INDETERMINATE:
308                    atLeastOneError = True
309
310                    # there are no rules about exactly what status data
311                    # should be returned here, so like in the combining
312                    # also, we'll just track the first error
313                    if firstIndeterminateStatus == None:
314                        firstIndeterminateStatus = match.getStatus()
315
316            # if we got here, then nothing matched, so we'll either return
317            # INDETERMINATE or NO_MATCH
318            if atLeastOneError:
319                return MatchResult(MatchResult.INDETERMINATE,
320                                       firstIndeterminateStatus)
321            else:
322                return MatchResult(MatchResult.NO_MATCH)
323
324        else:
325            # this is just an optimization, since the loop above will
326            # actually handle this case, but this is just a little
327            # quicker way to handle an empty bag
328            return MatchResult(MatchResult.NO_MATCH)
329   
330    def evaluateMatch(self, inputs, context):
331        '''Private helper that evaluates an individual match'''
332       
333        # evaluate the function
334        result = function.evaluate(inputs, context)
335
336        # if it was indeterminate, then that's what we return immediately
337        if result.indeterminate():
338            return MatchResult(MatchResult.INDETERMINATE,
339                               result.getStatus())
340
341        # otherwise, we figure out if it was a match
342        bool = result.getAttributeValue()
343
344        if bool.getValue():
345            return MatchResult(MatchResult.MATCH)
346        else:
347            return MatchResult(MatchResult.NO_MATCH)
348
349    def encode(self, output, indenter=None):
350        '''Encodes this TargetMatch</code> into its XML representation
351        and writes this encoding to the given <code>OutputStream</code> with no
352        indentation.
353        @param output a stream into which the XML-encoded data is written'''
354        raise NotImplementedError()
355   
356     
357class FunctionBase(XacmlBase):
358    FUNCTION_NS = "urn:oasis:names:tc:xacml:1.0:function:"
359   
360    def __init__(self, 
361                 functionName, 
362                 functionId=None, 
363                 paramType=None,
364                 paramIsBag=False,
365                 numParams=0, 
366                 minParams=0,
367                 returnType='', 
368                 returnsBag=False):
369         
370        self.functionName = functionName
371        self.functionId = functionId
372        self.returnType = None
373        self.returnsBag = False
374   
375        self.singleType = True;
376   
377        self.paramType = paramType
378        self.paramIsBag = paramIsBag
379        self.numParams = numParams
380        self.minParams = minParams
381       
382 
383    def _setFunctionName(self, functionName):
384          if functionName not in self.__class__.supportedIdentifiers:
385              functionList = ', '.join(self.__class__.supportedIdentifiers)
386              raise TypeError("Function name [%s] is not on of the recognised "
387                              "types: %s" % (functionName, functionList))
388          self._functionName = functionName
389         
390    def _getFunctionName(self):
391          return getattr(self, '_functionName', None)
392   
393    functionName = property(fset=_setFunctionName,
394                                    fget=_getFunctionName)
395         
396    def checkInputs(self, inputs):
397        '''Checks that the given inputs are of the right types, in the right
398        order, and are the right number for this function to evaluate.'''
399        raise NotImplementedError()
400           
401    def checkInputsNoBag(self, inputs):
402        '''Checks that the given inputs are of the right types, in the right
403        order, and are the right number for this function to evaluate.'''
404        raise NotImplementedError()
405 
406    def evaluate(self, inputs, context):
407        '''Evaluates the Function using the given inputs.'''
408        raise NotImplementedError()
409     
410    def evalArgs(self, params, context, args):
411        '''Evaluates each of the parameters, in order, filling in the argument
412        array with the resulting values. If any error occurs, this method
413        returns the error, otherwise null is returned, signalling that
414        evaluation was successful for all inputs, and the resulting argument
415        list can be used.
416       
417        @param params a list of Evaluatable objects representing the parameters
418        to evaluate
419        @param context the representation of the request
420        @param args an array as long as the params list that will, on return,
421        contain the AttributeValues generated from evaluating all parameters
422
423        @return None if no errors were encountered, otherwise
424        an EvaluationResult representing the error
425        '''
426        index = 0
427
428        for eval in params:
429            # get and evaluate the next parameter
430            result = eval.evaluate(context)
431
432            # If there was an error, pass it back...
433            if result.indeterminate():
434                return result
435
436            # ...otherwise save it and keep going
437            args[index] = result.getAttributeValue()
438            index += 1
439           
440        return None
441
442# TODO: Condition classes - minimal implementation until opportunity to fully
443# implement   
444class Function(XacmlBase):
445    def __init__(self, *arg, **kw):
446        raise NotImplementedError()
447
448    @classmethod
449    def getInstance(cls, root):
450        raise NotImplementedError()
451
452class BagFunction(FunctionBase):
453    def __init__(self, *arg, **kw):
454        raise NotImplementedError()
455
456class SetFunction(FunctionBase):
457    def __init__(self, *arg, **kw):
458        raise NotImplementedError()
459       
460class ConditionBagFunction(BagFunction):
461    def __init__(self, *arg, **kw):
462        raise NotImplementedError()
463       
464class ConditionSetFunction(FunctionBase):
465    def __init__(self, *arg, **kw):
466        raise NotImplementedError()
467       
468class HigherOrderFunction(Function):
469    def __init__(self, *arg, **kw):
470        raise NotImplementedError()
471
472# TODO: Function classes - minimal implementation until opportunity to fully
473# implement                                   
474class LogicalFunction(FunctionBase):
475
476    def __init__(self, *arg, **kw):
477        raise NotImplementedError()
478
479class NOfFunction(FunctionBase):
480   
481    def __init__(self, *arg, **kw):
482        raise NotImplementedError()
483       
484class NotFunction(FunctionBase):
485   
486    def __init__(self, *arg, **kw):
487        raise NotImplementedError()
488       
489class ComparisonFunction(FunctionBase):
490   
491    def __init__(self, *arg, **kw):
492        raise NotImplementedError()
493
494class MatchFunction(FunctionBase):
495    NAME_REGEXP_STRING_MATCH = \
496          "urn:oasis:names:tc:xacml:1.0:function:regexp-string-match"
497    NAME_RFC822NAME_MATCH = \
498          "urn:oasis:names:tc:xacml:1.0:function:rfc822Name-match"
499    NAME_X500NAME_MATCH = \
500          "urn:oasis:names:tc:xacml:1.0:function:x500Name-match"     
501
502    supportedIdentifiers = (NAME_REGEXP_STRING_MATCH, 
503                            NAME_RFC822NAME_MATCH,
504                            NAME_X500NAME_MATCH)
505   
506    lut = {
507          NAME_REGEXP_STRING_MATCH: 'regexpStringMatch',
508          NAME_RFC822NAME_MATCH:    'rfc822NameMatch',
509          NAME_X500NAME_MATCH:      'x500NameMatch'
510    }
511   
512    def __init__(self, functionName, **kw):
513          super(MatchFunction, self).__init__(functionName, **kw)
514
515    def regexpStringMatch(self, regex, val):
516          return re.match(regex, val) is not None
517   
518    def rfc822NameMatch(self, *inputs):
519        raise NotImplementedError()
520   
521    def x500NameMatch(self, *inputs):
522        raise NotImplementedError()
523   
524    def evaluate(self, inputs, context):
525          matchFunction = getattr(self, MatchFunction.lut[self.functionName])
526          match = matchFunction(self, *inputs)
527          if match:
528                return EvaluationResult(status=Status.STATUS_OK)
529
530
531class EqualFunction(FunctionBase):
532    supportedIdentifiers = (
533          "urn:oasis:names:tc:xacml:1.0:function:anyURI-equal",
534          "urn:oasis:names:tc:xacml:1.0:function:base64Binary-equal",
535          "urn:oasis:names:tc:xacml:1.0:function:boolean-equal",
536          "urn:oasis:names:tc:xacml:1.0:function:date-equal",
537          "urn:oasis:names:tc:xacml:1.0:function:dateTime-equal",
538          "urn:oasis:names:tc:xacml:1.0:function:dayTimeDuration-equal",
539          "urn:oasis:names:tc:xacml:1.0:function:double-equal",
540          "urn:oasis:names:tc:xacml:1.0:function:hexBinary-equal",
541          "urn:oasis:names:tc:xacml:1.0:function:integer-equal",
542          "urn:oasis:names:tc:xacml:1.0:function:rfc822Name-equal",
543          "urn:oasis:names:tc:xacml:1.0:function:string-equal",
544          "urn:oasis:names:tc:xacml:1.0:function:time-equal",
545          "urn:oasis:names:tc:xacml:1.0:function:x500Name-equal",
546          "urn:oasis:names:tc:xacml:1.0:function:yearMonthDuration-equal"
547    )
548
549    (NAME_ANYURI_EQUAL,
550    NAME_BASE64BINARY_EQUAL,
551    NAME_BOOLEAN_EQUAL,
552    NAME_DATE_EQUAL,
553    NAME_DATETIME_EQUAL,
554    NAME_DAYTIME_DURATION_EQUAL,
555    NAME_DOUBLE_EQUAL,
556    NAME_HEXBINARY_EQUAL,
557    NAME_INTEGER_EQUAL,
558    NAME_RFC822NAME_EQUAL,
559    NAME_STRING_EQUAL,
560    NAME_TIME_EQUAL,
561    NAME_X500NAME_EQUAL,
562    NAME_YEARMONTH_DURATION_EQUAL) = supportedIdentifiers
563
564    lut = {
565          NAME_STRING_EQUAL: 'stringEqual'
566    }
567   
568    typeMap = {NAME_STRING_EQUAL: basestring}
569   
570    def __init__(self, functionName, **kw):
571          super(EqualFunction, self).__init__(functionName, **kw)
572
573    def evaluate(self, inputs, evaluationCtx):
574        function = EqualFunction.lut.get(self.functionName)
575        if function is None:
576            if self.functionName in supportedIdentifiers:
577                raise NotImplementedError("No implementation is available for "
578                                          "%s" % self.functionName)           
579            else:
580                raise AttributeError('function name "%s" not recognised '
581                                     'for %s' % (self.functionName,
582                                                 self.__class__.__name__))
583                                 
584        return getattr(self, function)(inputs, evaluationCtx)
585   
586    def stringEqual(self, inputs, evaluationCtx):
587        result = self.evalArgs(inputs, context, argValues)
588        if result is not None:
589            return result
590         
591        return EvaluationResult(argValues[0] == argValues[1])
592   
593    def getArgumentType(functionName):
594        datatype = EqualFunction.typeMap.get(functionName);
595        if datatype is None:
596            raise AttributeError("Not a standard function: %s" % functionName)
597         
598        return datatype
599
600class AddFunction(FunctionBase):
601   
602    def __init__(self, *arg, **kw):
603        raise NotImplementedError()
604           
605class SubtractFunction(FunctionBase):
606   
607    def __init__(self, *arg, **kw):
608        raise NotImplementedError()
609           
610class MultiplyFunction(FunctionBase):
611   
612    def __init__(self, *arg, **kw):
613        raise NotImplementedError()
614           
615class DivideFunction(FunctionBase):
616   
617    def __init__(self, *arg, **kw):
618        raise NotImplementedError()
619           
620class ModFunction(FunctionBase):
621   
622    def __init__(self, *arg, **kw):
623        raise NotImplementedError()
624
625class AbsFunction(FunctionBase):
626   
627    def __init__(self, *arg, **kw):
628        raise NotImplementedError()
629
630class RoundFunction(FunctionBase):
631   
632    def __init__(self, *arg, **kw):
633        raise NotImplementedError()
634
635class FloorFunction(FunctionBase):
636   
637    def __init__(self, *arg, **kw):
638        raise NotImplementedError()
639
640class DateMathFunction(FunctionBase):
641   
642    def __init__(self, *arg, **kw):
643        raise NotImplementedError()
644
645class GeneralBagFunction(BagFunction):
646   
647    def __init__(self, *arg, **kw):
648        raise NotImplementedError()
649
650class NumericConvertFunction(FunctionBase):
651   
652    def __init__(self, *arg, **kw):
653        raise NotImplementedError()
654
655class StringNormalizeFunction(FunctionBase):
656   
657    def __init__(self, *arg, **kw):
658        raise NotImplementedError()
659
660class GeneralSetFunction(SetFunction):
661   
662    def __init__(self, *arg, **kw):
663        raise NotImplementedError()
664   
665class MapFunction(Function):       
666    supportedIdentifiers = ()
667    NAME_MAP = FunctionBase.FUNCTION_NS + "map"
668   
669    def __init__(self, *arg, **kw):
670        raise NotImplementedError()
671
672    @classmethod
673    def getInstance(cls, root):
674        raise NotImplementedError()
675   
676class FunctionProxy():
677
678    def getInstance(self, root):
679        raise NotImplementedError()
680
681class MapFunctionProxy(FunctionProxy):
682
683    def getInstance(self, root):
684        return MapFunction.getInstance(root)
685
686
687class UnknownIdentifierException(Exception):
688    pass
689
690class FunctionTypeException(Exception):
691    pass
692
693class ParsingException(Exception):
694    pass
695
696class FunctionFactory(XacmlBase):
697    '''Factory used to create all functions. There are three kinds of factories:
698    general, condition, and target. These provide functions that can be used
699    anywhere, only in a condition's root and only in a target (respectively).
700   
701    Note that all functions, except for abstract functions, are singletons, so
702    any instance that is added to a factory will be the same one returned
703    from the create methods. This is done because most functions don't have
704    state, so there is no need to have more than one, or to spend the time
705    creating multiple instances that all do the same thing.'''
706
707    defaultFactoryProxy = StandardFunctionFactory()                 
708           
709    @classmethod
710    def getTargetInstance(cls):
711        '''Returns the default FunctionFactory that will only provide those
712        functions that are usable in Target matching.
713       
714        @return a FunctionFactory for target functions'''
715        return cls.defaultFactoryProxy.getTargetFactory()
716       
717    @classmethod 
718    def getConditionInstance(cls): 
719        '''Returns the default FunctionFactory that provides access to all the
720        functions. These Functions are a superset of the Condition functions.
721       
722        @return a FunctionFactory for all functions
723        '''
724        return cls.defaultFactoryProxy.getConditionFactory()
725   
726    @classmethod
727    def getGeneralInstance(cls): 
728        '''Sets the default factory. Note that this is just a place holder for
729        now, and will be replaced with a more useful mechanism soon.'''
730        return cls.defaultFactoryProxy.getGeneralFactory()
731   
732   
733    def addFunction(self, function):
734        '''Adds the function to the factory. Most functions have no state, so
735        the singleton model used here is typically desirable. The factory will
736        not enforce the requirement that a Target or Condition matching
737        function must be boolean.
738       
739        @param function the Function to add to the factory
740        '''
741        raise NotImplementedError()
742       
743    def addAbstractFunction(self, functionProxy, identity):
744        '''Adds the abstract function proxy to the factory. This is used for
745        those functions which have state, or change behaviour (for instance
746        the standard map function, which changes its return type based on
747        how it is used).
748       
749        @param proxy the FunctionProxy to add to the factory
750        @param identity the function's identifier
751        '''
752        raise NotImplementedError()       
753   
754    def getSupportedFunctions(self):
755        '''Returns the function identifiers supported by this factory.
756       
757        @return a Set of Strings'''
758        raise NotImplementedError()
759
760    def createFunction(self, identity):
761        '''Tries to get an instance of the specified function.
762       
763        @param identity the name of the function
764        '''       
765        raise NotImplementedError()
766   
767    def createAbstractFunction(self, identity, root):
768        '''Tries to get an instance of the specified abstract function.
769       
770        @param identity the name of the function
771        @param root the DOM root containing info used to create the function
772        '''
773        raise NotImplementedError()
774
775
776class FunctionFactoryProxy(XacmlBase):
777    '''A simple proxy interface used to install new FunctionFactorys.
778    The three kinds of factory (Target, Condition, and General) are tied
779    together in this interface because implementors writing new factories
780    should always implement all three types and provide them together'''
781    def getTargetFactory():
782        raise NotImplementedError()
783
784    def getConditionFactory():
785        raise NotImplementedError()
786
787    def getGeneralFactory():
788        raise NotImplementedError()
789
790
791class BasicFunctionFactoryProxy(FunctionFactoryProxy):
792    '''A simple utility class that manages triples of function factories.'''
793   
794    # the triple of factories
795    targetFactory = None
796    conditionFactory = None
797    generalFactory = None
798
799    def __init__(targetFactory, conditionFactory, generalFactory): 
800        '''Creates a new proxy.
801       
802        @param targetFactory the target factory provided by this proxy
803        @param conditionFactory the target condition provided by this proxy
804        @param generalFactory the general factory provided by this proxy
805        '''
806        self.targetFactory = targetFactory
807        self.conditionFactory = conditionFactory
808        self.generalFactory = generalFactory
809   
810    def getTargetFactory():
811        return targetFactory
812
813    def getConditionFactory():
814        return conditionFactory
815
816    def getGeneralFactory():
817        return generalFactory
818   
819
820class BaseFunctionFactory(FunctionFactory):
821    '''This is a basic implementation of <code>FunctionFactory</code>. It
822    implements the insertion and retrieval methods, but it doesn't actually
823    setup the factory with any functions. It also assumes a certain model
824    with regard to the different kinds of functions (Target, Condition, and
825    General). For this reason, you may want to re-use this class, or you
826    may want to extend FunctionFactory directly, if you're writing a new
827    factory implementation.
828   
829    Note that while this class is thread-safe on all creation methods, it
830    is not safe to add support for a new function while creating an instance
831    of a function. This follows from the assumption that most people will
832    initialize these factories up-front, and then start processing without
833    ever modifying the factories. If you need these mutual operations to
834    be thread-safe, then you should write a wrapper class that implements
835    the right synchronization.
836    '''
837   
838    def __init__(self, 
839                 superset=None, 
840                 supportedFunctions=[],
841                 supportedAbstractFunctions={}):
842        '''Sets a "superset factory". This is useful since
843        the different function factories (Target, Condition, and General)
844        have a superset relationship (Condition functions are a superset
845        of Target functions, etc.). Adding a function to this factory will
846        automatically add the same function to the superset factory.
847
848        Constructor that defines the initial functions supported by this
849        factory but doesn't use a superset factory.
850
851        Constructor that defines the initial functions supported by this
852        factory but doesn't use a superset factory.
853
854        Constructor that defines the initial functions supported by this
855        factory and uses a superset factory. Note that the functions
856        supplied here are not propagated up to the superset factory, so
857        you must either make sure the superset factory is correctly
858        initialized or use BaseFunctionFactory(FunctionFactory)
859        and then manually add each function.
860       
861        @param supportedFunctions a Set of Functions
862        @param supportedAbstractFunctions a mapping from URI to
863                                          FunctionProxy
864       
865        @param supportedFunctions a Set of Functions
866        @param supportedAbstractFunctions a mapping from URI to FunctionProxy
867       
868        @param superset the superset factory or None'''
869       
870        # the backing maps for the Function objects
871        self.functionMap = {}
872   
873        # the superset factory chained to this factory
874        self.superset = superset
875     
876        for function in supportedFunctions:
877            self.functionMap[function.functionId] = function
878       
879        for id in supportedAbstractFunctions.keys():
880            proxy = supportedAbstractFunctions.get(id)
881            self.functionMap[id] = proxy
882 
883    def addFunction(self, function):
884        '''Adds the function to the factory. Most functions have no state, so
885        the singleton model used here is typically desirable. The factory will
886        not enforce the requirement that a Target or Condition matching
887        function must be boolean.
888       
889        @param function the Function to add to the factory
890        @raise TypeError if the function's identifier is already used or if the
891        function is non-boolean (when this is a Target or Condition factory)
892        '''
893        id = function.functionId
894
895        # make sure this doesn't already exist
896        if id in self.functionMap:
897            raise TypeError("function %s already exists" % id)
898
899        # add to the superset factory
900        if self.superset != None:
901            self.superset.addFunction(function)
902
903        # Add to this factory
904        self.functionMap[id] = function
905   
906       
907    def addAbstractFunction(self, proxy, id):
908        '''Adds the abstract function proxy to the factory. This is used for
909        those functions which have state, or change behaviour (for instance
910        the standard map function, which changes its return type based on
911        how it is used).
912       
913        @param proxy: the FunctionProxy to add to the factory
914        @param id: the function's identifier
915       
916        @raise TypeError if the function's identifier is already used'''
917
918        # make sure this doesn't already exist
919        if id in self.functionMap:
920            raise TypeError("function already exists")
921
922        # add to the superset factory
923        if self.superset != None:
924            self.superset.addAbstractFunction(proxy, id)
925
926        # finally, add to this factory
927        functionMap[id] = proxy
928   
929
930    def getSupportedFunctions(self): 
931        '''Returns the function identifiers supported by this factory.
932       
933        @return a list of strings'''
934   
935        functions = self.functionMap.keys()
936
937        if self.superset != None:
938            functions += self.superset.getSupportedFunctions()
939
940        return functions
941   
942
943    def createFunction(self, identity):
944        '''Tries to get an instance of the specified function.
945       
946        @param identity the name of the function
947       
948        @throws UnknownIdentifierException if the name isn't known
949        @throws FunctionTypeException if the name is known to map to an
950                                      abstract function, and should therefore
951                                      be created through createAbstractFunction
952        '''
953        entry = self.functionMap.get(identity)
954        if entry is not None:
955            if isinstance(entry, Function): 
956                return entry
957            else:
958                # this is actually a proxy, which means the other create
959                # method should have been called
960                raise FunctionTypeException("function is abstract")   
961        else:
962            # we couldn't find a match
963            raise UnknownIdentifierException("functions of type %s are not "
964                                             "supported by this factory" % 
965                                             identity)       
966   
967   
968    def createAbstractFunction(identity, root):
969        '''Tries to get an instance of the specified abstract function.
970       
971        @param identity the name of the function
972        @param root the DOM root containing info used to create the function
973        @param xpathVersion the version specified in the containing policy, or
974                            None if no version was specified
975       
976        @throws UnknownIdentifierException if the name isn't known
977        @throws FunctionTypeException if the name is known to map to a
978                                      concrete function, and should therefore
979                                      be created through createFunction
980        @throws ParsingException if the function can't be created with the
981                                 given inputs'''
982   
983        entry = self.functionMap.get(identity)
984        if entry is not None:
985            if isinstance(entry, FunctionProxy): 
986                try: 
987                    return entry.getInstance(root)
988               
989                except Exception, e:
990                    raise ParsingException("Couldn't create abstract function "
991                                           "%s: %s" % identity, e)     
992            else:
993                # this is actually a concrete function, which means that
994                # the other create method should have been called
995                raise FunctionTypeException("function is concrete")
996           
997        else:
998            raise UnknownIdentifierException("Abstract functions of type %s "
999                                             "are not supported by this "
1000                                             "factory" % identity)
1001
1002
1003class StandardFunctionFactory(BaseFunctionFactory):
1004    '''This factory supports the standard set of functions specified in XACML
1005    1.0 and 1.1. It is the default factory used by the system, and imposes
1006    a singleton pattern insuring that there is only ever one instance of
1007    this class.
1008    <p>
1009    Note that because this supports only the standard functions, this
1010    factory does not allow the addition of any other functions. If you call
1011    addFunction on an instance of this class, an exception
1012    will be thrown. If you need a standard factory that is modifiable,
1013    you can either create a new BaseFunctionFactory (or some
1014    other implementation of FunctionFactory) populated with
1015    the standard functions from getStandardFunctions or
1016    you can use getNewFactoryProxy to get a proxy containing
1017    a new, modifiable set of factories.'''
1018
1019
1020    # the three singleton instances
1021    targetFactory = None
1022    conditionFactory = None
1023    generalFactory = None
1024
1025    # the three function sets/maps that we use internally
1026    targetFunctions = None
1027    conditionFunctions = None
1028    generalFunctions = None
1029
1030    targetAbstractFunctions = None
1031    conditionAbstractFunctions = None
1032    generalAbstractFunctions = None
1033
1034    # the set/map used by each singleton factory instance
1035    supportedFunctions = None
1036    supportedAbstractFunctions = None
1037
1038   
1039    def __init__(self, supportedFunctions, supportedAbstractFunctions): 
1040        '''Creates a new StandardFunctionFactory, making sure that the default
1041        maps are initialized correctly. Standard factories can't be modified,
1042        so there is no notion of supersetting since that's only used for
1043        correctly propagating new functions.'''
1044        super(StandardFunctionFactory, self).__init__(supportedFunctions, 
1045                                                    supportedAbstractFunctions)
1046
1047        self.supportedFunctions = supportedFunctions
1048        self.supportedAbstractFunctions = supportedAbstractFunctions
1049   
1050
1051   
1052   
1053    def _initTargetFunctions(self): 
1054        '''Private initializer for the target functions. This is only ever
1055        called once.'''
1056        log.info("Initializing standard Target functions")
1057
1058        # Emulate a list with unique items using a dict with only the keys set
1059        StandardFunctionFactory.targetFunctions = {}
1060
1061        # add EqualFunction
1062        StandardFunctionFactory.targetFunctions.fromkeys(
1063                                    EqualFunction.supportedIdentifiers)
1064
1065        # add LogicalFunction
1066        StandardFunctionFactory.targetFunctions.fromkeys(
1067                                    LogicalFunction.supportedIdentifiers)
1068       
1069        # add NOfFunction
1070        StandardFunctionFactory.targetFunctions.fromkeys(
1071                                    NOfFunction.supportedIdentifiers)
1072       
1073        # add NotFunction
1074        StandardFunctionFactory.targetFunctions.fromkeys(
1075                                    NotFunction.supportedIdentifiers)
1076       
1077        # add ComparisonFunction
1078        StandardFunctionFactory.targetFunctions.fromkeys(
1079                                    ComparisonFunction.supportedIdentifiers)
1080
1081        # add MatchFunction
1082        StandardFunctionFactory.targetFunctions.fromkeys(
1083                                    MatchFunction.supportedIdentifiers)
1084
1085        StandardFunctionFactory.targetAbstractFunctions = {}
1086   
1087   
1088    def _initConditionFunctions(self): 
1089        '''Private initializer for the condition functions. This is only ever
1090        called once.'''
1091        log.info("Initializing standard Condition functions")
1092
1093        if StandardFunctionFactory.targetFunctions is None:
1094            self._initTargetFunctions()
1095
1096        StandardFunctionFactory.conditionFunctions = \
1097            StandardFunctionFactory.targetFunctions.copy()
1098
1099        # add condition functions from BagFunction
1100        conditionFunctions.fromkeys(ConditionBagFunction.supportedIdentifiers)
1101       
1102        # add condition functions from SetFunction
1103        conditionFunctions.fromkeys(ConditionSetFunction.supportedIdentifiers)
1104       
1105        # add condition functions from HigherOrderFunction
1106        conditionFunctions.fromkeys(HigherOrderFunction.supportedIdentifiers)
1107
1108        StandardFunctionFactory.conditionAbstractFunctions = \
1109            StandardFunctionFactory.targetAbstractFunctions.copy()
1110   
1111
1112    def _initGeneralFunctions(self):     
1113        '''Private initializer for the general functions. This is only ever
1114        called once.'''
1115   
1116        log.info("Initializing standard General functions")
1117
1118        if StandardFunctionFactory.conditionFunctions is None:
1119            self._initConditionFunctions()
1120
1121        StandardFunctionFactory.generalFunctions = \
1122            StandardFunctionFactory.conditionFunctions.copy()
1123
1124        # add AddFunction
1125        StandardFunctionFactory.generalFunctions.fromkeys(
1126            AddFunction.supportedIdentifiers)
1127           
1128        # add SubtractFunction
1129        StandardFunctionFactory.generalFunctions.fromkeys(
1130            SubtractFunction.supportedIdentifiers)
1131           
1132        # add MultiplyFunction
1133        StandardFunctionFactory.generalFunctions.fromkeys(
1134            MultiplyFunction.supportedIdentifiers)
1135           
1136        # add DivideFunction
1137        StandardFunctionFactory.generalFunctions.fromkeys(
1138            DivideFunction.supportedIdentifiers)
1139           
1140        # add ModFunction
1141        StandardFunctionFactory.generalFunctions.fromkeys(
1142            ModFunction.supportedIdentifiers)
1143       
1144        # add AbsFunction
1145        StandardFunctionFactory.generalFunctions.fromkeys(
1146            AbsFunction.supportedIdentifiers)
1147           
1148        # add RoundFunction
1149        StandardFunctionFactory.generalFunctions.fromkeys(
1150            RoundFunction.supportedIdentifiers)
1151           
1152        # add FloorFunction
1153        StandardFunctionFactory.generalFunctions.fromkeys(
1154            FloorFunction.supportedIdentifiers)
1155       
1156        # add DateMathFunction
1157        StandardFunctionFactory.generalFunctions.fromkeys(
1158            DateMathFunction.supportedIdentifiers)
1159           
1160        # add general functions from BagFunction
1161        StandardFunctionFactory.generalFunctions.fromkeys(
1162            GeneralBagFunction.supportedIdentifiers)
1163           
1164        # add NumericConvertFunction
1165        StandardFunctionFactory.generalFunctions.fromkeys(
1166            NumericConvertFunction.supportedIdentifiers)
1167           
1168        # add StringNormalizeFunction
1169        StandardFunctionFactory.generalFunctions.fromkeys(
1170            StringNormalizeFunction.supportedIdentifiers)
1171       
1172        # add general functions from SetFunction
1173        StandardFunctionFactory.generalFunctions.fromkeys(
1174            GeneralSetFunction.supportedIdentifiers)
1175           
1176        StandardFunctionFactory.generalAbstractFunctions = \
1177            StandardFunctionFactory.conditionAbstractFunctions.copy()
1178
1179        # Add the map function's proxy
1180        StandardFunctionFactory.generalAbstractFunctions[
1181                                    MapFunction.NAME_MAP] = MapFunctionProxy()
1182   
1183    @classmethod 
1184    def getTargetFactory(cls): 
1185        '''Returns a FunctionFactory that will only provide those functions
1186        that are usable in Target matching. This method enforces a singleton
1187        model, meaning that this always returns the same instance, creating
1188        the factory if it hasn't been requested before. This is the default
1189        model used by the FunctionFactory, ensuring quick
1190        access to this factory.
1191       
1192        @return a FunctionFactory for target functions'''
1193        if StandardFunctionFactory.targetFactory is None: 
1194            if StandardFunctionFactory.targetFunctions is None:
1195                StandardFunctionFactory._initTargetFunctions()
1196               
1197            if StandardFunctionFactory.targetFactory is None:
1198                StandardFunctionFactory.targetFactory=StandardFunctionFactory(
1199                            StandardFunctionFactory.targetFunctions,
1200                            StandardFunctionFactory.targetAbstractFunctions)
1201       
1202        return StandardFunctionFactory.targetFactory
1203
1204   
1205    @classmethod
1206    def getConditionFactory(cls): 
1207        '''Returns a FuntionFactory that will only provide those functions that
1208        are usable in the root of the Condition. These Functions are a
1209        superset of the Target functions. This method enforces a singleton
1210        model, meaning that this always returns the same instance, creating
1211        the factory if it hasn't been requested before. This is the default
1212        model used by the FunctionFactory, ensuring quick
1213        access to this factory.
1214   
1215        @return a FunctionFactory for condition functions
1216        '''
1217        if StandardFunctionFactory.conditionFactory is None:
1218            if StandardFunctionFactory.conditionFunctions is None:
1219                StandardFunctionFactory._initConditionFunctions()
1220               
1221            if StandardFunctionFactory.conditionFactory is None:
1222                StandardFunctionFactory.conditionFactory = \
1223                    StandardFunctionFactory(
1224                           StandardFunctionFactory.conditionFunctions,
1225                           StandardFunctionFactory.conditionAbstractFunctions)       
1226
1227        return StandardFunctionFactory.conditionFactory
1228   
1229
1230    @classmethod
1231    def getGeneralFactory(cls): 
1232        '''Returns a FunctionFactory that provides access to all the functions.
1233        These Functions are a superset of the Condition functions. This method
1234        enforces a singleton model, meaning that this always returns the same
1235        instance, creating the factory if it hasn't been requested before.
1236        This is the default model used by the FunctionFactory,
1237        ensuring quick access to this factory.
1238       
1239        @return a FunctionFactory for all functions'''
1240   
1241        if StandardFunctionFactory.generalFactory is None:
1242            if StandardFunctionFactory.generalFunctions is None:
1243                StandardFunctionFactory._initGeneralFunctions()
1244               
1245                StandardFunctionFactory.generalFactory = \
1246                    StandardFunctionFactory(
1247                            StandardFunctionFactory.generalFunctions,
1248                            StandardFunctionFactory.generalAbstractFunctions)
1249               
1250        return StandardFunctionFactory.generalFactory
1251
1252
1253    def getStandardFunctions(self):
1254        '''Returns the set of functions that this standard factory supports.
1255       
1256        @return a Set of Functions'''
1257        return tuple(self.supportedFunctions.keys())
1258       
1259    def getStandardAbstractFunctions(self):
1260        '''Returns the set of abstract functions that this standard factory
1261        supports as a mapping of identifier to proxy.
1262       
1263        @return a Map mapping URIs to FunctionProxys'''
1264        return tuple(self.supportedAbstractFunctions.keys())
1265   
1266   
1267    @classmethod
1268    def getNewFactoryProxy(cls): 
1269        '''A convenience method that returns a proxy containing newly created
1270        instances of BaseFunctionFactorys that are correctly
1271        supersetted and contain the standard functions and abstract functions.
1272        These factories allow adding support for new functions.
1273       
1274        @return a new proxy containing new factories supporting the standard
1275        functions'''
1276       
1277        general = StandardFunctionFactory.getGeneralFactory()
1278           
1279        newGeneral=BaseFunctionFactory(general.getStandardFunctions(),
1280                                       general.getStandardAbstractFunctions())
1281
1282        condition = StandardFunctionFactory.getConditionFactory()
1283       
1284        newCondition = BaseFunctionFactory(newGeneral,
1285                                    condition.getStandardFunctions(),
1286                                    condition.getStandardAbstractFunctions())
1287
1288        target = StandardFunctionFactory.getTargetFactory()
1289        newTarget = BaseFunctionFactory(newCondition,
1290                                    target.getStandardFunctions(),
1291                                    target.getStandardAbstractFunctions())
1292
1293        return BasicFunctionFactoryProxy(newTarget, newCondition, newGeneral)
1294   
1295
1296   
1297   
1298    def addFunction(self, function):
1299        '''Always throws an exception, since support for new functions may not
1300        be added to a standard factory.
1301       
1302        @param function the Function to add to the factory       
1303        @raise NotImplementedError'''
1304   
1305        raise NotImplementedError("a standard factory cannot support new "
1306                                  "functions")
1307   
1308   
1309    def addAbstractFunction(self, proxy, identity):
1310        '''Always throws an exception, since support for new functions may not
1311        be added to a standard factory.
1312       
1313        @param proxy the FunctionProxy to add to the factory
1314        @param identity the function's identifier
1315       
1316        @raise NotImplementedError always'''
1317        raise NotImplementedError("a standard factory cannot support new "
1318                                  "functions")
1319
1320   
1321class Status(XacmlBase):
1322    STATUS_MISSING_ATTRIBUTE = \
1323          "urn:oasis:names:tc:xacml:1.0:status:missing-attribute"
1324    STATUS_OK = "urn:oasis:names:tc:xacml:1.0:status:ok"
1325    STATUS_PROCESSING_ERROR = \
1326          "urn:oasis:names:tc:xacml:1.0:status:processing-error"
1327    STATUS_SYNTAX_ERROR = \
1328          "urn:oasis:names:tc:xacml:1.0:status:syntax-error" 
1329     
1330class EvaluationResult(XacmlBase):
1331    def __init__(self, 
1332                 attributeValue=None, 
1333                 status=None, 
1334                 indeterminate=False):
1335        self.status = status
1336        self.attributeValue = attributeValue
1337        self.indeterminate = indeterminate
1338
1339
1340class Evaluatable(XacmlBase):
1341    '''Generic interface that is implemented by all objects that can appear in
1342    an ApplyType. This lets the evaluation code of Apply and
1343    functions iterate through their members and evaluate them, working only
1344    on the returned values or errors.'''
1345   
1346    def evaluate(self, context):
1347        '''Evaluates the object using the given context, and either returns an
1348        error or a resulting value.
1349   
1350        @param context the representation of the request
1351        @return the result of evaluation'''
1352        raise NotImplementedError()
1353
1354    def getType(self):
1355        '''Get the type of this object.  This may be the data type of an
1356        Attribute or the return type of an
1357        AttributeDesignator, etc.
1358   
1359        @return the type of data represented by this object'''
1360        raise NotImplementedError()
1361
1362    def evaluatesToBag(self):
1363        '''Tells whether evaluation will return a bag or a single value.
1364   
1365        @return true if evaluation will return a bag, false otherwise'''
1366        raise NotImplementedError()
1367
1368    def getChildren(self):
1369        '''Returns all children, in order, of this element in the Condition
1370        tree, or en empty set if this element has no children. In XACML 1.x,
1371        only the ApplyType ever has children.
1372   
1373        @return a list of Evaluatables'''
1374        raise NotImplementedError()
1375
1376    def encode(self, output, indenter=None):
1377        '''Encodes this Evaluatable into its XML representation and
1378        writes this encoding to the given OutputStream with
1379        indentation.
1380   
1381        @param output a stream into which the XML-encoded data is written
1382        @param indenter an object that creates indentation strings'''
1383        raise NotImplementedError()
1384
1385                   
1386class Effect(XacmlBase):
1387    def __str__(self):
1388        raise NotImplementedError()
1389         
1390class DenyEffect(Effect):
1391    def __str__(self):
1392        return 'deny'
1393         
1394class PermitEffect(Effect):
1395    def __str__(self):
1396        return 'permit'
1397
1398class Rule(XacmlBase):
1399    '''Consists of a condition, an effect, and a target.
1400    '''
1401    def __init__(self, conditions=[], effect=DenyEffect(), target=None):
1402        # Conditions are statements about attributes that upon evaluation
1403        # return either True, False, or Indeterminate.
1404        self.conditions = conditions
1405       
1406        # Effect is the intended consequence of the satisfied rule. It can
1407        # either take the value Permit or Deny.
1408        self.effect = effect
1409     
1410        # Target, as in the case of a policy, helps in determining whether or
1411        # not a rule is relevant for a request. The mechanism for achieving
1412        # this is also similar to how it is done in the case of a target for a
1413        # policy.
1414        self.target = target
1415       
1416    @classmethod
1417    def getInstance(cls, elem):
1418        root = elem.getroot()
1419        return Rule(conditions=conditions, effect=effect, target=target)
1420         
1421class Attribute(XacmlBase):
1422    def __init__(self, id, type=None, issuer=None, issueInstant=None, value=None):
1423        self.id = id
1424        self.type = type or value.__class__
1425        self.issuer = issuer
1426        self.issueInstant = issueInstant
1427        self.value = value
1428
1429       
1430class Request(XacmlBase):
1431    '''XACML Request XacmlBase
1432   
1433    TODO: refactor from this initial placeholder'''
1434    def __init__(self, subject, resource, action=None, environment={}):
1435          self.subject = subject
1436          self.resource = resource
1437          self.action = action
1438          self.environment = environment
1439
1440class Response(XacmlBase):
1441    pass
1442
1443
1444class PDP(XacmlBase):
1445    '''Modify PDPInterface to use the four XACML request designators: subject,
1446    resource, action and environment
1447   
1448    This is an initial iteration toward a complete XACML implementation'''
1449    def __init__(self, *arg, **kw):
1450          pass
1451   
1452    def evaluate(self, request):
1453          '''Make access control decision - override this in a derived class to
1454          implement the decision logic but this method may be called within
1455          the derived method to check input types
1456         
1457          @param request: request object containing the subject, resource,
1458          action and environment
1459          @type request: ndg.security.common.authz.xacml.Request
1460          @return reponse object
1461          @rtype: ndg.security.common.authz.xacml.Response
1462          '''
1463          raise NotImplementedError()
1464
1465
1466class RuleCombiningAlg(XacmlBase):
1467    id = None
1468
1469class DenyOverrides(RuleCombiningAlg):
1470   '''Deny-overrides: If any rule evaluates to Deny, then the final
1471   authorization decision is also Deny.'''
1472   id = 'Deny-overrides'
1473   
1474class OrderedDenyOverrides(RuleCombiningAlg):
1475    '''Ordered-deny-overrides: Same as deny-overrides, except the order in
1476    which relevant rules are evaluated is the same as the order in which they
1477    are added in the policy.'''
1478    id = 'Ordered-deny-overrides'
1479   
1480class PermitOverrides(RuleCombiningAlg):
1481    '''Permit-overrides: If any rule evaluates to Permit, then the final
1482    authorization decision is also Permit.'''
1483   
1484class OrderedPermitOverrides(RuleCombiningAlg):
1485    '''Ordered-permit-overrides: Same as permit-overrides, except the order in
1486    which relevant rules are evaluated is the same as the order in which they
1487    are added in the policy.'''
1488    id = 'Ordered-permit-overrides'
1489   
1490class FirstApplicable(RuleCombiningAlg):
1491    '''First-applicable: The result of the first relevant rule encountered is
1492    the final authorization decision as well.'''
1493    id = 'First-applicable'
1494
1495
1496class EvaluationCtx(object):
1497
1498    # The standard URI for listing a resource's id
1499    RESOURCE_ID ="urn:oasis:names:tc:xacml:1.0:resource:resource-id"
1500
1501    # The standard URI for listing a resource's scope
1502    RESOURCE_SCOPE = "urn:oasis:names:tc:xacml:1.0:resource:scope"
1503
1504    # Resource scope of Immediate (only the given resource)
1505    SCOPE_IMMEDIATE = 0
1506
1507    # Resource scope of Children (the given resource and its direct
1508    # children)
1509    SCOPE_CHILDREN = 1
1510
1511    # Resource scope of Descendants (the given resource and all descendants
1512    # at any depth or distance)
1513    SCOPE_DESCENDANTS = 2
1514   
1515    def getRequestRoot(self):
1516        '''Returns the DOM root of the original RequestType XML document, if
1517        this context is backed by an XACML Request. If this context is not
1518        backed by an XML representation, then an exception is thrown.'''
1519        raise NotImplementedError()
1520
1521    def getResourceId(self):
1522        '''Returns the identifier for the resource being requested.'''
1523        raise NotImplementedError()
1524
1525    def getScope(self):
1526        '''Returns the resource scope, which will be one of the three fields
1527        denoting Immediate, Children, or Descendants.'''
1528        raise NotImplementedError()
1529
1530    def setResourceId(self, resourceId):
1531        '''Changes the value of the resource-id attribute in this context. This
1532        is useful when you have multiple resources (ie, a scope other than
1533        IMMEDIATE), and you need to keep changing only the resource-id to
1534        evaluate the different effective requests.'''
1535        raise NotImplementedError()
1536
1537    def getCurrentTime(self):
1538        '''Returns the cached value for the current time. If the value has
1539        never been set by a call to <code>setCurrentTime</code>, or if caching
1540        is not enabled in this instance, then this will return null.'''
1541        raise NotImplementedError()
1542
1543    def setCurrentTime(self, currentTime):
1544        '''Sets the current time for this evaluation. If caching is not enabled
1545        for this instance then the value is ignored.
1546     
1547        @param currentTime the dynamically resolved current time'''
1548        raise NotImplementedError()
1549
1550    def getCurrentDate(self):
1551        '''Returns the cached value for the current date. If the value has
1552        never been set by a call to <code>setCurrentDate</code>, or if caching
1553        is not enabled in this instance, then this will return null.'''
1554        raise NotImplementedError()
1555
1556    def setCurrentDate(self, currentDate):
1557        '''Sets the current date for this evaluation. If caching is not enabled
1558        for this instance then the value is ignored.'''
1559        raise NotImplementedError()
1560
1561    def getCurrentDateTime(self):
1562        '''Returns the cached value for the current dateTime. If the value has
1563        never been set by a call to <code>setCurrentDateTime</code>, or if
1564        caching is not enabled in this instance, then this will return null.
1565        '''
1566        raise NotImplementedError()
1567
1568    def setCurrentDateTime(self, currentDateTime):
1569        '''Sets the current dateTime for this evaluation. If caching is not
1570        enabled for this instance then the value is ignored.
1571     
1572        @param currentDateTime the dynamically resolved current dateTime'''
1573        raise NotImplementedError()
1574
1575    def getSubjectAttribute(self, type, id, category):
1576        '''Returns available subject attribute value(s) ignoring the issuer.
1577     
1578        @param type the type of the attribute value(s) to find
1579        @param id the id of the attribute value(s) to find
1580        @param category the category the attribute value(s) must be in
1581     
1582        @return a result containing a bag either empty because no values were
1583        found or containing at least one value, or status associated with an
1584        Indeterminate result'''
1585        raise NotImplementedError()
1586
1587    def getSubjectAttribute(self, type, id, issuer, category):
1588        '''Returns available subject attribute value(s).
1589     
1590        @param type the type of the attribute value(s) to find
1591        @param id the id of the attribute value(s) to find
1592        @param issuer the issuer of the attribute value(s) to find or null
1593        @param category the category the attribute value(s) must be in
1594     
1595        @return a result containing a bag either empty because no values were
1596        found or containing at least one value, or status associated with an
1597        Indeterminate result'''
1598        raise NotImplementedError()
1599   
1600    def getResourceAttribute(self, type, id, issuer):
1601        '''Returns available resource attribute value(s).
1602     
1603        @param type the type of the attribute value(s) to find
1604        @param id the id of the attribute value(s) to find
1605        @param issuer the issuer of the attribute value(s) to find or null
1606     
1607        @return a result containing a bag either empty because no values were
1608        found or containing at least one value, or status associated with an
1609        Indeterminate result'''
1610        raise NotImplementedError()
1611
1612    def getActionAttribute(self, type, id, issuer):
1613        '''Returns available action attribute value(s).
1614     
1615        @param type the type of the attribute value(s) to find
1616        @param id the id of the attribute value(s) to find
1617        @param issuer the issuer of the attribute value(s) to find or null
1618     
1619        @return a result containing a bag either empty because no values were
1620        found or containing at least one value, or status associated with an
1621        Indeterminate result'''
1622        raise NotImplementedError()
1623
1624    def getEnvironmentAttribute(self, type, id, issuer):
1625        '''Returns available environment attribute value(s).
1626     
1627        @param type the type of the attribute value(s) to find
1628        @param id the id of the attribute value(s) to find
1629        @param issuer the issuer of the attribute value(s) to find or null
1630     
1631        @return a result containing a bag either empty because no values were
1632        found or containing at least one value, or status associated with an
1633        Indeterminate result'''
1634        raise NotImplementedError()
1635
1636    def getAttribute(self, contextPath, namespaceNode, type, xpathVersion):
1637        '''Returns the attribute value(s) retrieved using the given XPath
1638        expression.
1639     
1640        @param contextPath the XPath expression to search
1641        @param namespaceNode the DOM node defining namespace mappings to use,
1642                            or null if mappings come from the context root
1643        @param type the type of the attribute value(s) to find
1644        @param xpathVersion the version of XPath to use
1645     
1646        @return a result containing a bag either empty because no values were
1647       
1648        found or containing at least one value, or status associated with an
1649        Indeterminate result'''
1650        raise NotImplementedError()
1651
1652
1653class Apply(Evaluatable):
1654    '''Represents the XACML ApplyType and ConditionType XML types.'''
1655
1656    def __init__(self, function, evals, bagFunction=None, isCondition=False):
1657        '''Constructs an Apply object. Throws an
1658        IllegalArgumentException if the given parameter list
1659        isn't valid for the given function.
1660       
1661        @param function the Function to use in evaluating the elements in the
1662        apply
1663        @param evals the contents of the apply which will be the parameters
1664        to the function, each of which is an Evaluatable
1665        @param bagFunction the higher-order function to use
1666        @param isCondition Rrue if this Apply is a Condition, False otherwise
1667        '''
1668   
1669        # check that the given inputs work for the function
1670        inputs = evals
1671        if bagFunction is not None:
1672            inputs = [bagFunction]
1673            inputs += evals
1674       
1675        function.checkInputs(inputs)
1676
1677        # if everything checks out, then store the inputs
1678        self.function = function
1679        self.evals = tuple(evals)
1680        self.bagFunction = bagFunction
1681        self.isCondition = isCondition
1682   
1683   
1684    @staticmethod
1685    def getConditionInstance(root, xpathVersion):
1686        '''Returns an instance of an Apply based on the given DOM
1687        root node. This will actually return a special kind of
1688        Apply, namely an XML ConditionType, which is the root
1689        of the condition logic in a RuleType. A ConditionType is the same
1690        as an ApplyType except that it must use a FunctionId that returns
1691        a boolean value.
1692       
1693        @param root the DOM root of a ConditionType XML type
1694        @param xpathVersion the XPath version to use in any selectors or XPath
1695                            functions, or null if this is unspecified (ie, not
1696                            supplied in the defaults section of the policy)
1697       
1698        '''
1699        raise NotImplementedError()
1700         
1701    def getInstance(self, 
1702                    root, 
1703                    factory=None, 
1704                    isCondition=False, 
1705                    xpathVersion=None):
1706        '''Returns an instance of Apply based on the given DOM root.
1707       
1708        @param root the DOM root of an ApplyType XML type
1709        @param xpathVersion the XPath version to use in any selectors or XPath
1710                            functions, or null if this is unspecified (ie, not
1711                            supplied in the defaults section of the policy)'''
1712       
1713        raise NotImplementedError()
1714   
1715    @staticmethod
1716    def getFunction(root, version, factory):
1717        '''Helper method that tries to get a function instance'''
1718        raise NotImplementedError()
1719           
1720    def getFunction(self):
1721        '''Returns the Function used by this Apply.
1722       
1723        @return the Function'''
1724        return function
1725   
1726    def getChildren(self):
1727        '''Returns the List of children for this Apply.
1728        The List contains Evaluatables. The list is
1729        unmodifiable, and may be empty.
1730       
1731        @return a List of Evaluatables'''
1732        return self.evals
1733   
1734    def getHigherOrderFunction(self):
1735        '''Returns the higher order bag function used by this Apply
1736        if it exists, or null if no higher order function is used.
1737       
1738        @return the higher order Function or null'''
1739        return self.bagFunction
1740   
1741    def isCondition(self):
1742        '''Returns whether or not this ApplyType is actually a ConditionType.
1743       
1744        @return whether or not this represents a ConditionType'''
1745        return isCondition
1746
1747    def evaluate(self, context):
1748        '''Evaluates the apply object using the given function. This will in
1749        turn call evaluate on all the given parameters, some of which may be
1750        other Apply objects.
1751       
1752        @param context the representation of the request
1753       
1754        @return the result of trying to evaluate this apply object'''
1755        parameters = self.evals
1756
1757        # see if there is a higher-order function in here
1758        if bagFunction != None:
1759            # this is a special case, so we setup the parameters, starting
1760            # with the function
1761            parameters = [bagFunction]
1762
1763            # now we evaluate all the parameters, returning INDETERMINATE
1764            # if that's what any of them return, and otherwise tracking
1765            # all the AttributeValues that get returned
1766            for eval in self.evals:
1767                result = eval.evaluate(context)
1768               
1769                # in a higher-order case, if anything is INDETERMINATE, then
1770                # we stop right away
1771                if result.indeterminate():
1772                    return result
1773
1774                parameters.add(result.getAttributeValue())
1775           
1776        # now we can call the base function
1777        return function.evaluate(parameters, context)
1778         
1779    def getType(self):
1780        '''Returns the type of attribute that this object will return on a call
1781        to evaluate. In practice, this will always be the same as
1782        the result of calling getReturnType on the function used
1783        by this object.
1784       
1785        @return the type returned by evaluate'''
1786        return self.function.getReturnType()
1787     
1788    def evaluatesToBag(self):
1789        '''Returns whether or not the Function will return a bag
1790        of values on evaluation.
1791       
1792        @return true if evaluation will return a bag of values, false otherwise
1793        '''
1794        return self.function.returnsBag()
1795
1796    def encode(self, output, indenter):
1797        '''Encodes this Apply into its XML representation and
1798        writes this encoding to the given OutputStream with
1799        indentation.
1800       
1801        @param output a stream into which the XML-encoded data is written
1802        @param indenter an object that creates indentation strings'''
1803        raise NotImplementedError()
Note: See TracBrowser for help on using the repository browser.