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

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

ndg.security.common.authz.msi: added additional logging for PDP and PIP.
ndg.security.common.authz.xacml: added SetFunction? and ConditionSetFunction? implementations to enable support got at-least-one-member of rules.

Line 
1"""XACML Attribute module
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__ = "03/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$"
45
46import logging
47log = logging.getLogger(__name__)
48
49from ndg.security.common.utils import getLocalName
50from ndg.security.common.authz.xacml.cond.eval import Evaluatable, \
51    EvaluationResult
52
53class AttributeDesignator(object):
54    elemNames = [n+'AttributeDesignator' for n in ('Action', 'Environment',
55                                                   'Resource', 'Subject')]
56    targetCodes = range(4)
57    targetLUT = dict(zip(elemNames, targetCodes))
58   
59    (ACTION_TARGET, 
60    ENVIRONMENT_TARGET, 
61    RESOURCE_TARGET, 
62    SUBJECT_TARGET) = targetCodes
63   
64    def __init__(self, 
65                 target, 
66                 type, 
67                 attributeId, 
68                 mustBePresent=False, 
69                 issuer=None):
70        if target not in AttributeDesignator.targetCodes:
71            raise AttributeError("Target code must be one of %r; input code "
72                                 "is %r" % (AttributeDesignator.targetCodes,
73                                            target))
74        self.target = target
75        self.type = type
76        self.id = attributeId
77        self.mustBePresent = mustBePresent
78        self.issuer = issuer
79
80    @classmethod
81    def getInstance(cls, elem, target):
82        """Create a new instance from an ElementTree element
83        @type elem: ElementTree.Element
84        @param elem: AttributeDesignator XML element
85        @type target: int
86        @param target: target code
87        @rtype: AttributeDesignator
88        @return: new AttributeDesignator instance
89        """
90        localName = getLocalName(elem)
91        if localName not in cls.elemNames:
92            raise AttributeError("Element name [%s] is not a recognised "
93                                 "AttributeDesignator name %r" % 
94                                 (localName, cls.elemNames))
95           
96       
97        if target not in cls.targetCodes:
98            raise AttributeError("Target code [%d] is not a recognised "
99                                 "AttributeDesignator target code %r" % 
100                                 (localName, cls.targetCodes))
101           
102        id = elem.attrib['AttributeId']
103        type = elem.attrib['DataType']
104        mustBePresent=elem.attrib.get('mustBePresent','false').lower()=='true'
105        issuer = elem.attrib.get('issuer')
106        return cls(target, type, id, mustBePresent=mustBePresent,issuer=issuer)
107
108
109class AttributeValue(Evaluatable):
110    '''The base type for all datatypes used in a policy or request/response,
111    this abstract class represents a value for a given attribute type.
112    All the required types defined in the XACML specification are
113    provided as instances of AttributeValues. If you want to
114    provide a new type, extend this class and implement the
115    equals(Object) and hashCode methods from
116    Object, which are used for equality checking.'''
117   
118    def __init__(self, type):
119        '''@param type the attribute's type
120        '''
121        self.type = type
122       
123    def _getIsBag(self, val):
124        '''BagAttribute should override this to True'''
125        return False
126   
127    isBag = property(fget=_getIsBag)
128   
129    def evaluatesToBag(self): 
130        '''Returns whether or not this value is actually a bag of values. This
131        is a required interface from Evaluatable, but the
132        more meaningful isBag method is used by
133        AttributeValues, so this method is declared as final
134        and calls the isBag method for this value.
135       
136        @return true if this is a bag of values, false otherwise
137        '''
138        return self.isBag
139
140    def getChildren(self):
141        '''Always returns an empty list since values never have children.
142       
143        @return an empty List'''
144        return []
145   
146    def evaluate(self, context):
147        '''Implements the required interface from Evaluatable.
148        Since there is nothing to evaluate in an attribute value, the default
149        result is just this instance. Override this method if you want
150        special behavior, like a dynamic value.
151       
152        @param context the representation of the request
153       
154        @return a successful evaluation containing this value'''   
155        return EvaluationResult(self)
156
157    def _encode(self):
158        '''Encodes the value in a form suitable for including in XML data like
159        a request or an obligation. This must return a value that could in
160        turn be used by the factory to create a new instance with the same
161        value.
162       
163        @return a string form of the value'''
164        raise NotImplementedError()
165   
166    def encode(self, output=None):
167        '''Encodes this AttributeValue into its XML representation
168        and writes this encoding to the given OutputStream with
169        no indentation. This will always produce the version used in a
170        policy rather than that used in a request, so this is equivalent
171        to calling encodeWithTags(true) and then stuffing that
172        into a stream.
173       
174        @param output a stream into which the XML-encoded data is written'''
175        if output is not None:
176            output.write(self.encodeWithTags())
177        else:
178            return self._encode()
179   
180    def encodeWithTags(includeType=True):
181        '''Encodes the value and includes the AttributeValue XML tags so that
182        the resulting string can be included in a valid XACML policy or
183        Request/Response. The boolean parameter lets you include
184        the DataType attribute, which is required in a policy but not allowed
185        in a Request or Response.
186       
187        @param includeType include the DataType XML attribute if
188                           true, exclude if false
189       
190        @return a String encoding including the XML tags
191        '''
192        if includeType:
193            return '<AttributeValue DataType="%s">%s</AttributeValue>' % \
194                    (self.type, self.encode())
195        else:
196            return "<AttributeValue>%s</AttributeValue>" % self.encode()
197                           
198class StringAttribute(AttributeValue):
199    identifier = "http://www.w3.org/2001/XMLSchema#string"
200   
201    def __init__(self, value=''):
202        self.value = value
203        super(StringAttribute, self).__init__(self.__class__.identifier)
204       
205    def __str__(self):
206        return self.value
207   
208    def _encode(self):
209        return self.value
210   
211    @classmethod
212    def getInstance(cls, root=None, value=None):
213        """Make a new StrinAttribute instance from an element or string value
214        @type root: ElementTree.Element
215        @param root: XML element
216        @type value: basestring
217        @param value: value to set string to
218        """
219        if root is not None:
220            value = root.text
221        elif value is None:
222            raise TypeError('"elem" or "value" keyword must be set')
223           
224        return StringAttribute(value=value)
225   
226
227class AnyURIAttribute(AttributeValue):
228    identifier = "http://www.w3.org/2001/XMLSchema#anyURI"
229   
230    def __init__(self, value=''):
231        self.value = value
232        super(AnyURIAttribute, self).__init__(self.__class__.identifier)
233       
234    def __str__(self):
235        return self.value
236   
237    def _encode(self):
238        return self.value
239
240class Base64BinaryAttribute(AttributeValue):
241    identifier = "http://www.w3.org/2001/XMLSchema#base64Binary"
242   
243    def __init__(self, value=0):
244        self.value = value
245        super(Base64BinaryAttribute, self).__init__(self.__class__.identifier)
246       
247    def __str__(self):
248        return self.value
249   
250    def _encode(self):
251        return self.value
252     
253class BooleanAttribute(AttributeValue):   
254    identifier = "http://www.w3.org/2001/XMLSchema#boolean"
255   
256    def __init__(self, value=False):
257        self.value = value
258        super(BooleanAttribute, self).__init__(self.__class__.identifier)
259       
260    def __str__(self):
261        return self.value
262   
263    def _encode(self):
264        return str(self.value)
265   
266class DateAttribute(AttributeValue):
267    identifier = "http://www.w3.org/2001/XMLSchema#date"
268   
269    def __init__(self, value=''):
270        self.value = value
271        super(DateAttribute, self).__init__(self.__class__.identifier)
272   
273class DateTimeAttribute(AttributeValue):
274    identifier = "http://www.w3.org/2001/XMLSchema#dateTime"
275   
276    def __init__(self, value=''):
277        self.value = value
278        super(DateTimeAttribute, self).__init__(self.__class__.identifier)
279   
280class DayTimeDurationAttribute(AttributeValue):
281    identifier = ("http://www.w3.org/TR/2002/WD-xquery-operators-20020816#"
282                  "dayTimeDuration")
283   
284    def __init__(self, value=''):
285        self.value = value
286        super(DayTimeDurationAttribute, self).__init__(
287                                                    self.__class__.identifier)
288   
289class DoubleAttribute(AttributeValue):
290    identifier = "http://www.w3.org/2001/XMLSchema#double"
291   
292    def __init__(self, value=0.):
293        self.value = value
294        super(DoubleAttribute, self).__init__(self.__class__.identifier)
295   
296class HexBinaryAttribute(AttributeValue):
297    identifier = "http://www.w3.org/2001/XMLSchema#hexBinary"
298   
299    def __init__(self, value=0x0):
300        self.value = value
301        super(HexBinaryAttribute, self).__init__(self.__class__.identifier)
302   
303class IntegerAttribute(AttributeValue):
304    identifier = "http://www.w3.org/2001/XMLSchema#integer"
305   
306    def __init__(self, value=0):
307        self.value = value
308        super(IntegerAttribute, self).__init__(self.__class__.identifier)
309   
310class RFC822NameAttribute(AttributeValue):
311    identifier = "urn:oasis:names:tc:xacml:1.0:data-type:rfc822Name"
312   
313    def __init__(self, value=''):
314        self.value = value
315        super(RFC822NameAttribute, self).__init__(self.__class__.identifier)
316       
317class TimeAttribute(AttributeValue):
318    identifier = "http://www.w3.org/2001/XMLSchema#time"
319   
320    def __init__(self, value=''):
321        self.value = value
322        super(TimeAttribute, self).__init__(self.__class__.identifier)
323
324class X500NameAttribute(AttributeValue):
325    identifier = "urn:oasis:names:tc:xacml:1.0:data-type:x500Name"
326
327    def __init__(self, value=''):
328        self.value = value
329        super(X500NameAttribute, self).__init__(self.__class__.identifier)
330
331class YearMonthDurationAttribute(AttributeValue):
332    identifier = ("http://www.w3.org/TR/2002/WD-xquery-operators-20020816#"
333                  "yearMonthDuration")
334   
335    def __init__(self, value=''):
336        self.value = value
337        super(YearMonthDurationAttribute, self).__init__(
338                                                    self.__class__.identifier)
339
340   
341class AttributeFactoryProxy(object):
342    '''A simple proxy interface used to install new AttributeFactory'''
343    @staticmethod
344    def getFactory():
345        raise NotImplementedError()
346
347             
348class AttributeFactory(object):
349    '''This is an abstract factory class for creating XACML attribute values.
350    There may be any number of factories available in the system, though
351    there is always one default factory used by the core code.
352    '''
353
354    # the proxy used to get the default factory
355    defaultFactoryProxy = AttributeFactoryProxy() 
356    defaultFactoryProxy.getFactory = lambda: \
357                                        StandardAttributeFactory.getFactory()
358             
359    @classmethod
360    def getInstance(cls):
361        '''Returns the default factory. Depending on the default factory's
362        implementation, this may return a singleton instance or new instances
363        with each invocation.
364   
365        @return: the default AttributeFactory
366        '''
367        return cls.defaultFactoryProxy.getFactory()   
368
369   
370    def addDatatype(self, id, proxy):
371        '''Adds a proxy to the factory, which in turn will allow new attribute
372        types to be created using the factory. Typically the proxy is
373        provided as an anonymous class that simply calls the getInstance
374        methods (or something similar) of some AttributeValue
375        class.
376       
377        @param id the name of the attribute type
378        @param proxy the proxy used to create new attributes of the given type
379       
380        @raise AttributeError if the given id is already in use
381        '''
382        raise NotImplementedError()
383   
384    def getSupportedDatatypes(self):
385        '''Returns the datatype identifiers supported by this factory.
386       
387        @return: a list of strings
388        '''
389        raise NotImplementedError()
390
391    def createValue(self, root, dataType=None, value=None):
392        '''Creates a value based on the given root element. The type of the
393        attribute is assumed to be present in the node as an XAML attribute
394        named DataType, as is the case with the AttributeValueType in the
395        policy schema. The value is assumed to be the first child of this node.
396       
397        @param: ElementTree.Element root of an attribute value     
398        @param dataType: the type of the attribute
399        @param value the text-encoded representation of an attribute's value
400        @return: a new AttributeValue   
401        @raise UnknownIdentifierException if the type in the node isn't
402                                           known to the factory
403        @raise ParsingException if the node is invalid or can't be parsed
404                                 by the appropriate proxy
405        '''
406        raise NotImplementedError() 
407   
408     
409class BaseAttributeFactory(AttributeFactory):
410    '''This is a basic implementation of AttributeFactory abstract class. It
411    implements the insertion and retrieval methods, but doesn't actually
412    setup the factory with any datatypes.
413 
414    Note that while this class is thread-safe on all creation methods, it
415    is not safe to add support for a new datatype while creating an instance
416    of a value. This follows from the assumption that most people will
417    initialize these factories up-front, and then start processing without
418    ever modifying the factories. If you need these mutual operations to
419    be thread-safe, then you should write a wrapper class that implements
420    the right synchronization.
421    '''
422
423    def __init__(self, attributeMap={}): 
424        # the map of proxies
425        self._attributeMap = attributeMap.copy()
426   
427   
428    def addDatatype(self, id, proxy):
429        '''Adds a proxy to the factory, which in turn will allow new attribute
430        types to be created using the factory. Typically the proxy is
431        provided as an anonymous class that simply calls the getInstance
432        methods (or something similar) of some AttributeValue
433        class.
434   
435        @param id: the name of the attribute type
436        @param proxy: the proxy used to create new attributes of the given type
437        '''
438        # make sure this doesn't already exist
439        if id in self._attributeMap:
440            raise AttributeError("Data type %s already exists" % id)
441
442        self._attributeMap[id] = proxy
443   
444    def getSupportedDatatypes(self): 
445        '''Returns the datatype identifiers supported by this factory.
446       
447        @return: a list of types'''
448        return self._attributeMap.keys()
449   
450    def createValue(self, root=None, dataType=None, value=None):
451        '''Creates a value based on the given elem root node. The type of the
452        attribute is assumed to be present in the node as an XACML attribute
453        named DataType, as is the case with the
454        AttributeValueType in the policy schema. The value is assumed to be
455        the first child of this node.
456       
457        @param root: the root elem of an attribute value
458        @param dataType: the type of the attribute
459        @param value: the text-encoded representation of an attribute's value
460        @return: a new AttributeValue instance
461        @raise UnknownIdentifierException: if the type in the node isn't
462                                            known to the factory
463        @raise ParsingException: if the node is invalid or can't be parsed
464        by the appropriate proxy
465        '''
466        if dataType is None:
467            dataType = root.attrib["DataType"]
468
469        proxy = self._attributeMap.get(dataType)
470        if proxy is None:
471            raise UnknownIdentifierException("Attributes of type %s aren't "
472                                             "supported." % dataType)
473           
474        if root is not None:
475            param = root
476        elif value is not None:
477            param = value
478        else:
479            raise TypeError('A "root" or "value" keyword must be set')
480           
481        try:
482            return proxy.getInstance(param)
483        except Exception, e: 
484            raise ParsingException("Couldn't create %s attribute based on "
485                                   "element: %s" % (dataType, e))
486           
487
488class StandardAttributeFactory(BaseAttributeFactory):
489    '''This factory supports the standard set of datatypes specified in XACML
490    1.0 and 1.1. It is the default factory used by the system, and imposes
491    a singleton pattern insuring that there is only ever one instance of
492    this class.
493   
494    Note that because this supports only the standard datatypes, this
495    factory does not allow the addition of any other datatypes. If you call
496    addDatatype on an instance of this class, an exception
497    will be thrown. If you need a standard factory that is modifiable, you
498    should create a new BaseAttributeFactory (or some other
499    AttributeFactory) and configure it with the standard
500    datatypes using addStandardDatatypes (or, in the case of
501    BaseAttributeFactory, by providing the datatypes in the
502    constructor).'''
503   
504    factoryInstance = None
505   
506    # the datatypes supported by this factory
507    supportedDatatypes = None
508   
509    def __init__(self):
510        """Initialise attribute map from supportedDatatypes class var by
511        calling BaseAttributeFactory constructor
512        """
513        super(StandardAttributeFactory, self).__init__(
514                    attributeMap=StandardAttributeFactory.supportedDatatypes)
515       
516    @classmethod
517    def _initDatatypes(cls): 
518        '''Private initializer for the supported datatypes. This isn't called
519        until something needs these values, and is only called once.'''
520        log.info("Initializing standard datatypes")
521
522        # TODO: implement Attribute proxy classes - maybe not needed?
523        cls.supportedDatatypes = {
524#            BooleanAttribute.identifier: BooleanAttributeProxy(),
525#            StringAttribute.identifier: StringAttributeProxy(),
526#            DateAttribute.identifier: DateAttributeProxy(),
527#            TimeAttribute.identifier: TimeAttributeProxy(),
528#            DateTimeAttribute.identifier: DateTimeAttributeProxy(),
529#            DayTimeDurationAttribute.identifier: DayTimeDurationAttributeProxy(),
530#            YearMonthDurationAttribute.identifier: YearMonthDurationAttributeProxy(),
531#            DoubleAttribute.identifier: DoubleAttributeProxy(),
532#            IntegerAttribute.identifier: IntegerAttributeProxy(),
533#            AnyURIAttribute.identifier: AnyURIAttributeProxy(),
534#            HexBinaryAttribute.identifier: HexBinaryAttributeProxy(),
535#            Base64BinaryAttribute.identifier: Base64BinaryAttributeProxy(),
536#            X500NameAttribute.identifier: X500NameAttributeProxy(),
537#            RFC822NameAttribute.identifier: RFC822NameAttributeProxy()
538            BooleanAttribute.identifier: BooleanAttribute(),
539            StringAttribute.identifier: StringAttribute(),
540            DateAttribute.identifier: DateAttribute(),
541            TimeAttribute.identifier: TimeAttribute(),
542            DateTimeAttribute.identifier: DateTimeAttribute(),
543            DayTimeDurationAttribute.identifier: DayTimeDurationAttribute(),
544            YearMonthDurationAttribute.identifier: YearMonthDurationAttribute(),
545            DoubleAttribute.identifier: DoubleAttribute(),
546            IntegerAttribute.identifier: IntegerAttribute(),
547            AnyURIAttribute.identifier: AnyURIAttribute(),
548            HexBinaryAttribute.identifier: HexBinaryAttribute(),
549            Base64BinaryAttribute.identifier: Base64BinaryAttribute(),
550            X500NameAttribute.identifier: X500NameAttribute(),
551            RFC822NameAttribute.identifier: RFC822NameAttribute()
552        }
553
554    @classmethod
555    def getFactory(cls):
556        '''Returns an instance of this factory. This method enforces a
557        singleton model, meaning that this always returns the same instance,
558        creating the factory if it hasn't been requested before. This is the
559        default model used by the AttributeFactory, ensuring quick access to
560        this factory.
561       
562        @return the factory instance
563        @classmethod'''
564        if cls.factoryInstance is None:
565            cls._initDatatypes()
566            cls.factoryInstance = cls()
567           
568        return cls.factoryInstance
569   
570    def addDatatype(self, id, proxy):
571        '''
572        @param id the name of the attribute type
573        @param proxy the proxy used to create new attributes of the given type
574       
575        @raise: NotImplementedError: standard factory can't be
576        modified
577        '''
578        raise NotImplementedError("a standard factory cannot support new data "
579                                  "types")
580
581
582   
583
584
585   
Note: See TracBrowser for help on using the repository browser.