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

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

fix to getLocalName import

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