source: TI12-security/trunk/python/ndg.security.saml/saml/xml/etree.py @ 5601

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.saml/saml/xml/etree.py@5601
Revision 5601, 48.3 KB checked in by pjkersha, 11 years ago (diff)

Adding Assertion parsing functionality - not finished!

Line 
1"""Implementation of SAML 2.0 for NDG Security - ElementTree module for
2ElementTree representation of SAML objects
3
4NERC DataGrid Project
5
6This implementation is adapted from the Java OpenSAML implementation.  The
7copyright and licence information are included here:
8
9Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
10
11Licensed under the Apache License, Version 2.0 (the "License");
12you may not use this file except in compliance with the License.
13You may obtain a copy of the License at
14
15http://www.apache.org/licenses/LICENSE-2.0
16
17Unless required by applicable law or agreed to in writing, software
18distributed under the License is distributed on an "AS IS" BASIS,
19WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20See the License for the specific language governing permissions and
21limitations under the License.
22"""
23__author__ = "P J Kershaw"
24__date__ = "23/07/09"
25__copyright__ = "(C) 2009 Science and Technology Facilities Council"
26__contact__ = "Philip.Kershaw@stfc.ac.uk"
27__license__ = "BSD - see LICENSE file in top-level directory"
28__contact__ = "Philip.Kershaw@stfc.ac.uk"
29__revision__ = "$Id$"
30import logging
31log = logging.getLogger(__name__)
32import re
33
34try: # python 2.5
35    from xml.etree import cElementTree, ElementTree
36except ImportError:
37    # if you've installed it yourself it comes this way
38    import cElementTree, ElementTree
39
40from saml.saml2.core import SAMLObject, Attribute, AttributeStatement, \
41    Assertion, Conditions, AttributeValue, AttributeQuery, Subject, NameID, \
42    Issuer, SAMLVersion, Response, Status, StatusCode , Advice, \
43    XSStringAttributeValue, XSGroupRoleAttributeValue
44from saml.common.xml import SAMLConstants
45from saml.xml import XMLObjectParseError
46from saml.utils import SAMLDateTime
47from saml.xml import QName as GenericQName
48
49
50# Generic ElementTree Helper classes
51class QName(ElementTree.QName):
52    """Extend ElementTree implementation for improved attribute access support
53    """ 
54
55    # ElementTree tag is of the form {namespace}localPart.  getNs extracts the
56    # namespace from within the brackets but if not found returns ''
57    getNs = staticmethod(lambda tag: getattr(re.search('(?<=\{).+(?=\})', tag),
58                                             'group', 
59                                             str)())
60                                             
61    getLocalPart = staticmethod(lambda tag: tag.rsplit('}',1)[-1])
62
63    def __init__(self, input, tag=None, prefix=None):
64        """
65        @type input: basestring
66        @param input: ElementTree style namespace URI + tag name -
67        {namespace URI}tag - OR if tag keyword is set, the namespace URI alone
68        @type tag: basestring / None
69        @param tag: element tag name.  If None, input must contain the
70        namespace URI and tag name in the ElementTree form {namespace URI}tag.
71        @type prefix: basestring / None
72        @param prefix: namespace prefix
73        """
74       
75        ElementTree.QName.__init__(self, input, tag=tag)
76       
77        if tag:
78            self.namespaceURI = input
79            self.localPart = tag
80        else:
81            # No tag provided namespace and locatPart of QN must be parsed from
82            # the namespace
83            self.namespaceURI = QName.getNs(input)
84            self.localPart = QName.getLocalPart(input)
85           
86        self.prefix = prefix
87   
88    def _getPrefix(self):
89        return self.__prefix
90
91    def _setPrefix(self, value):
92        self.__prefix = value
93   
94    prefix = property(_getPrefix, _setPrefix, None, "Prefix")
95
96    def _getLocalPart(self):
97        return self.__localPart
98   
99    def _setLocalPart(self, value):
100        self.__localPart = value
101       
102    localPart = property(_getLocalPart, _setLocalPart, None, "LocalPart")
103
104    def _getNamespaceURI(self):
105        return self.__namespaceURI
106
107    def _setNamespaceURI(self, value):
108        self.__namespaceURI = value
109 
110    namespaceURI = property(_getNamespaceURI, _setNamespaceURI, None, 
111                            "Namespace URI'")
112
113    @classmethod
114    def fromGeneric(cls, genericQName):
115        '''Cast the generic QName type in saml.xml to the ElementTree specific
116        implementation'''
117        if not isinstance(genericQName, GenericQName):
118            raise TypeError("Expecting %r for QName, got %r" % (GenericQName,
119                                                        type(genericQName)))
120           
121        qname = cls(genericQName.namespaceURI, 
122                    tag=genericQName.localPart,
123                    prefix=genericQName.prefix)
124        return qname
125   
126   
127def prettyPrint(*arg, **kw):
128    '''Lightweight pretty printing of ElementTree elements'''
129   
130    # Keep track of namespace declarations made so they're not repeated
131    declaredNss = []
132   
133    _prettyPrint = PrettyPrint(declaredNss)
134    return _prettyPrint(*arg, **kw)
135
136
137class PrettyPrint(object):
138    def __init__(self, declaredNss):
139        self.declaredNss = declaredNss
140   
141    @staticmethod
142    def estrip(elem):
143        ''' Just want to get rid of unwanted whitespace '''
144        if elem is None:
145            return ''
146        else:
147            # just in case the elem is another simple type - e.g. int -
148            # wrapper it as a string
149            return str(elem).strip()
150       
151    def __call__(self, elem, indent='', html=0, space=' '*4):
152        '''Most of the work done in this wrapped function - wrapped so that
153        state can be maintained for declared namespace declarations during
154        recursive calls using "declaredNss" above''' 
155        strAttribs = []
156        for attr, attrVal in elem.attrib.items():
157            nsDeclaration = ''
158           
159            attrNamespace = QName.getNs(attr)
160            if attrNamespace:
161                nsPrefix = ElementTree._namespace_map.get(attrNamespace)
162                if nsPrefix is None:
163                    raise KeyError('prettyPrint: missing namespace "%s" for ' 
164                                   'ElementTree._namespace_map'%attrNamespace)
165               
166                attr = "%s:%s" % (nsPrefix, QName.getLocalPart(attr))
167               
168                if attrNamespace not in self.declaredNss:
169                    nsDeclaration = ' xmlns:%s="%s"' % (nsPrefix,attrNamespace)
170                    self.declaredNss.append(attrNamespace)
171               
172            strAttribs.append('%s %s="%s"' % (nsDeclaration, attr, attrVal))
173           
174        strAttrib = ''.join(strAttribs)
175       
176        namespace = QName.getNs(elem.tag)
177        nsPrefix = ElementTree._namespace_map.get(namespace)
178        if nsPrefix is None:
179            raise KeyError('prettyPrint: missing namespace "%s" for ' 
180                           'ElementTree._namespace_map' % namespace)
181           
182        tag = "%s:%s" % (nsPrefix, QName.getLocalPart(elem.tag))
183       
184        # Put in namespace declaration if one doesn't already exist
185        # FIXME: namespace declaration handling is wrong for handling child
186        # element scope
187        if namespace in self.declaredNss:
188            nsDeclaration = ''
189        else:
190            nsDeclaration = ' xmlns:%s="%s"' % (nsPrefix, namespace)
191            self.declaredNss.append(namespace)
192           
193        result = '%s<%s%s%s>%s' % (indent, tag, nsDeclaration, strAttrib, 
194                                   PrettyPrint.estrip(elem.text))
195       
196        children = len(elem)
197        if children:
198            for child in elem:
199                declaredNss = self.declaredNss[:]
200                _prettyPrint = PrettyPrint(declaredNss)
201                result += '\n'+ _prettyPrint(child, indent=indent+space) 
202               
203            result += '\n%s%s</%s>' % (indent,
204                                       PrettyPrint.estrip(child.tail),
205                                       tag)
206        else:
207            result += '</%s>' % tag
208           
209        return result
210
211
212# ElementTree SAML wrapper classes
213class ConditionsElementTree(Conditions):
214    """ElementTree based XML representation of Conditions class
215    """
216   
217    @classmethod
218    def create(cls, conditions):
219        """Make a tree of a XML elements based on the assertion conditions"""
220       
221        if not isinstance(conditions, Conditions):
222            raise TypeError("Expecting %r type got: %r" % (Conditions,
223                                                           conditions))
224       
225        notBeforeStr = SAMLDateTime.toString(conditions.notBefore)
226        notOnOrAfterStr = SAMLDateTime.toString(conditions.notOnOrAfter)
227        attrib = {
228            cls.NOT_BEFORE_ATTRIB_NAME: notBeforeStr,
229            cls.NOT_ON_OR_AFTER_ATTRIB_NAME: notOnOrAfterStr,
230        }
231       
232        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
233        elem = ElementTree.Element(tag, **attrib)
234       
235        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
236                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
237
238        for condition in conditions.conditions:
239            raise NotImplementedError("Conditions list creation is not "
240                                      "implemented")
241               
242        return elem
243               
244class AssertionElementTree(Assertion):
245    """ElementTree based XML representation of Assertion class
246    """
247   
248    @classmethod
249    def create(cls, 
250               assertion, 
251               **attributeValueElementTreeFactoryKw):
252        """Make a tree of a XML elements based on the assertion"""
253       
254        if not isinstance(assertion, Assertion):
255            raise TypeError("Expecting %r type got: %r"%(Assertion, assertion))
256       
257        issueInstant = SAMLDateTime.toString(assertion.issueInstant)
258        attrib = {
259            cls.ID_ATTRIB_NAME: assertion.id,
260            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
261           
262            # Nb. Version is a SAMLVersion instance and requires explicit cast
263            cls.VERSION_ATTRIB_NAME: str(assertion.version)
264        }
265        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
266        elem = ElementTree.Element(tag, **attrib)
267       
268        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
269                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
270       
271        if assertion.issuer is not None:
272            issuerElem = IssuerElementTree.create(assertion.issuer)
273            elem.append(issuerElem)
274       
275        if assertion.subject is not None:
276            subjectElem = SubjectElementTree.create(assertion.subject)
277            elem.append(subjectElem)
278
279        if assertion.advice:
280            raise NotImplementedError("Assertion Advice creation is not "
281                                      "implemented")
282
283        if assertion.conditions is not None:
284            conditionsElem = ConditionsElementTree.create(assertion.conditions)
285            elem.append(conditionsElem)
286           
287        for statement in assertion.statements:
288            raise NotImplementedError("Assertion Statement creation is not "
289                                      "implemented")
290       
291        for authnStatement in assertion.authnStatements:
292            raise NotImplementedError("Assertion Authentication Statement "
293                                      "creation is not implemented")
294       
295        for authzDecisionStatement in assertion.authzDecisionStatements:
296            raise NotImplementedError("Assertion Authorisation Decision "
297                                      "Statement creation is not implemented")
298           
299        for attributeStatement in assertion.attributeStatements:
300            attributeStatementElem = AttributeStatementElementTree.create(
301                                        attributeStatement,
302                                        **attributeValueElementTreeFactoryKw)
303            elem.append(attributeStatementElem)
304       
305        return elem
306
307   
308    @classmethod
309    def parse(cls, elem):
310        """Make a tree of a XML elements based on the assertion"""
311        if not ElementTree.iselement(elem):
312            raise TypeError("Expecting %r input type for parsing; got %r" %
313                            (ElementTree.Element, elem))
314
315        localName = QName.getLocalPart(elem.tag)
316        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
317            raise XMLObjectParseError("No \"%s\" element found" %
318                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
319       
320       
321        # Unpack attributes from top-level element
322        attributeValues = []
323        for attributeName in (cls.VERSION_ATTRIB_NAME,
324                              cls.ISSUE_INSTANT_ATTRIB_NAME,
325                              cls.ID_ATTRIB_NAME):
326            attributeValue = elem.attrib.get(attributeName)
327            if attributeValue is None:
328                raise XMLObjectParseError('No "%s" attribute found in "%s" '
329                                 'element' %
330                                 (attributeName,
331                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
332               
333            attributeValues.append(attributeValue)
334       
335        attributeQuery = AttributeQuery()
336        attributeQuery.version = SAMLVersion(attributeValues[0])
337        if attributeQuery.version != SAMLVersion.VERSION_20:
338            raise NotImplementedError("Parsing for %r is implemented for "
339                                      "SAML version %s only; version %s is " 
340                                      "not supported" % 
341                                      (cls,
342                                       SAMLVersion(SAMLVersion.VERSION_20),
343                                       SAMLVersion(attributeQuery.version)))
344           
345        attributeQuery.issueInstant = SAMLDateTime.fromString(
346                                                            attributeValues[1])
347        attributeQuery.id = attributeValues[2]
348       
349        for childElem in elem:
350            localName = QName.getLocalPart(childElem.tag)
351            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
352                # Parse Issuer
353                assertion.issuer = IssuerElementTree.parse(childElem)
354               
355            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
356                # Parse Issuer
357                assertion.subject = SubjectElementTree.parse(childElem)
358               
359            elif localName == Advice.DEFAULT_ELEMENT_LOCAL_NAME:
360                raise NotImplementedError("Assertion Advice creation is not "
361                                          "implemented")
362            elif localName == "Conditions":
363           
364            statement in assertion.statements:
365            raise NotImplementedError("Assertion Statement creation is not "
366                                      "implemented")
367       
368        for authnStatement in assertion.authnStatements:
369            raise NotImplementedError("Assertion Authentication Statement "
370                                      "creation is not implemented")
371       
372        for authzDecisionStatement in assertion.authzDecisionStatements:
373            raise NotImplementedError("Assertion Authorisation Decision "
374                                      "Statement creation is not implemented")
375           
376        for attributeStatement in assertion.attributeStatements:
377            attributeStatementElem = AttributeStatementElementTree.create(
378                                        attributeStatement,
379                                        **attributeValueElementTreeFactoryKw)
380            elem.append(attributeStatementElem)
381       
382        return assertion
383
384 
385class AttributeStatementElementTree(AttributeStatement):
386    """ElementTree XML representation of AttributeStatement"""
387   
388    @classmethod
389    def create(cls, 
390               attributeStatement, 
391               **attributeValueElementTreeFactoryKw):
392        if not isinstance(attributeStatement, AttributeStatement):
393            raise TypeError("Expecting %r type got: %r" % (AttributeStatement, 
394                                                           attributeStatement))
395           
396        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
397        elem = ElementTree.Element(tag)
398        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
399                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
400
401        for attribute in attributeStatement.attributes:
402            # Factory enables support for multiple attribute types
403            attributeElem = AttributeElementTree.create(attribute,
404                                        **attributeValueElementTreeFactoryKw)
405            elem.append(attributeElem)
406       
407        return elem
408   
409
410class AttributeElementTree(Attribute):
411    """ElementTree XML representation of SAML Attribute object.  Extend
412    to make Attribute types""" 
413
414    @classmethod
415    def create(cls, 
416               attribute, 
417               **attributeValueElementTreeFactoryKw):
418        """Make 'Attribute' element"""
419       
420        if not isinstance(attribute, Attribute):
421            raise TypeError("Expecting %r type got: %r"%(Attribute, attribute))
422       
423        tag = str(QName.fromGeneric(Attribute.DEFAULT_ELEMENT_NAME))   
424        elem = ElementTree.Element(tag)
425        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
426                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
427       
428        if attribute.friendlyName:
429            elem.set(cls.FRIENDLY_NAME_ATTRIB_NAME, attribute.friendlyName) 
430             
431        if attribute.name:
432            elem.set(cls.NAME_ATTRIB_NAME, attribute.name)
433       
434        if attribute.nameFormat:
435            elem.set(cls.NAME_FORMAT_ATTRIB_NAME, attribute.nameFormat)
436
437        for attributeValue in attribute.attributeValues:
438            factory = AttributeValueElementTreeFactory(
439                                        **attributeValueElementTreeFactoryKw)
440           
441            attributeValueElementTree = factory(attributeValue)
442           
443            attributeValueElem=attributeValueElementTree.create(attributeValue)
444            elem.append(attributeValueElem)
445           
446        return elem
447 
448    @classmethod
449    def parse(cls, elem, **attributeValueElementTreeFactoryKw):
450        """Parse ElementTree element into a SAML Attribute object
451       
452        @type elem: ElementTree.Element
453        @param elem: Attribute as ElementTree XML element
454        @rtype: ndg.security.common.saml.Attribute
455        @return: SAML Attribute
456        """
457        if not ElementTree.iselement(elem):
458            raise TypeError("Expecting %r input type for parsing; got %r" %
459                            (ElementTree.Element, elem))
460
461        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
462            raise XMLObjectParseError("No \"%s\" element found" %
463                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
464           
465        attribute = Attribute()
466           
467        name = elem.attrib.get(cls.NAME_ATTRIB_NAME)
468        if name is not None:
469            attribute.name = name
470           
471        friendlyName = elem.attrib.get(cls.FRIENDLY_NAME_ATTRIB_NAME)
472        if friendlyName is not None:
473            attribute.friendlyName = friendlyName
474           
475        nameFormat = elem.attrib.get(cls.NAME_FORMAT_ATTRIB_NAME)   
476        if nameFormat is not None:
477            attribute.nameFormat = nameFormat
478
479        for childElem in elem:
480            localName = QName.getLocalPart(childElem.tag)
481            if localName != AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME:
482                raise XMLObjectParseError('Expecting "%s" element; found "%s"'%
483                                    (AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME,
484                                     localName))
485           
486            # Find XML type attribute to key which AttributeValue sub type to
487            # instantiate
488            attributeValueTypeId = None
489            for attribName, attribVal in childElem.attrib.items():
490                qname = QName(attribName)
491                if qname.localPart == "type":
492                    attributeValueTypeId = attribVal
493                    break
494               
495            if attributeValueTypeId is None:
496                raise XMLObjectParseError("Unable to determine type for "
497                                          "AttributeValue")
498               
499            factory = AttributeValueElementTreeFactory(
500                                        **attributeValueElementTreeFactoryKw)
501   
502            attributeValueElementTreeClass = factory(attributeValueTypeId)
503            attributeValue = attributeValueElementTreeClass.parse(childElem)
504            attribute.attributeValues.append(attributeValue)
505       
506        return attribute
507       
508   
509class AttributeValueElementTreeBase(AttributeValue):
510    """Base class ElementTree XML representation of SAML Attribute Value""" 
511   
512    @classmethod
513    def create(cls, attributeValue):
514        """Make 'Attribute' XML element"""
515
516        if not isinstance(attributeValue, AttributeValue):
517            raise TypeError("Expecting %r type got: %r" % (AttributeValue, 
518                                                           attributeValue))
519           
520        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))   
521        elem = ElementTree.Element(tag)
522        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
523                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
524
525        return elem
526
527
528class XSStringAttributeValueElementTree(AttributeValueElementTreeBase,
529                                        XSStringAttributeValue):
530    """ElementTree XML representation of SAML String type Attribute Value""" 
531   
532    @classmethod
533    def create(cls, attributeValue):
534        """Create an XML representation of the input SAML Attribute Value"""
535        elem = AttributeValueElementTreeBase.create(attributeValue)
536       
537        if not isinstance(attributeValue, XSStringAttributeValue):
538            raise TypeError("Expecting %r type got: %r" % 
539                            (XSStringAttributeValue, attributeValue)) 
540       
541        # Have to explicitly add namespace declaration here rather use
542        # ElementTree._namespace_map because the prefixes are used for
543        # attributes not element names       
544        elem.set("%s:%s" % (SAMLConstants.XMLNS_PREFIX, 
545                            SAMLConstants.XSD_PREFIX),
546                 SAMLConstants.XSD_NS)
547                                   
548        elem.set("%s:%s" % (SAMLConstants.XMLNS_PREFIX, 
549                            SAMLConstants.XSI_PREFIX),
550                 SAMLConstants.XSI_NS)
551       
552        elem.set("%s:%s" % (SAMLConstants.XSI_PREFIX, 'type'), 
553                 "%s:%s" % (SAMLConstants.XSD_PREFIX, 
554                            cls.TYPE_LOCAL_NAME))
555
556        elem.text = attributeValue.value
557
558        return elem
559
560    @classmethod
561    def parse(cls, elem):
562        """Parse ElementTree xs:string element into a SAML
563        XSStringAttributeValue object
564       
565        @type elem: ElementTree.Element
566        @param elem: Attribute value as ElementTree XML element
567        @rtype: ndg.security.common.saml.AttributeValue
568        @return: SAML Attribute value
569        """
570        if not ElementTree.iselement(elem):
571            raise TypeError("Expecting %r input type for parsing; got %r" %
572                            (ElementTree.Element, elem))
573
574        localName = QName.getLocalPart(elem.tag)
575        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
576            raise XMLObjectParseError("No \"%s\" element found" %
577                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
578       
579        # Parse the attribute type checking that it is set to the expected
580        # string type
581        typeQName = QName(SAMLConstants.XSI_NS, tag='type')
582       
583        typeValue = elem.attrib.get(str(typeQName), '')
584        typeValueLocalName = typeValue.split(':')[-1]
585        if typeValueLocalName != cls.TYPE_LOCAL_NAME:
586            raise XMLObjectParseError('Expecting "%s" type; got "%s"' %
587                                      (cls.TYPE_LOCAL_NAME,
588                                       typeValueLocalName))
589       
590        # Update namespace map as an XSI type has been referenced.  This will
591        # ensure the correct prefix is applied if it is re-serialised.
592        ElementTree._namespace_map[SAMLConstants.XSI_NS
593                                   ] = SAMLConstants.XSI_PREFIX
594                                     
595        attributeValue = XSStringAttributeValue()
596        if elem.text is not None:
597            attributeValue.value = elem.text.strip()
598
599        return attributeValue
600
601
602class XSGroupRoleAttributeValueElementTree(AttributeValueElementTreeBase,
603                                           XSGroupRoleAttributeValue):
604    """ElementTree XML representation of Earth System Grid custom Group/Role
605    Attribute Value""" 
606
607    @classmethod
608    def create(cls, attributeValue):
609        """Create an XML representation of the input SAML Attribute Value"""
610        elem = AttributeValueElementTreeBase.create(attributeValue)
611       
612        if not isinstance(attributeValue, XSGroupRoleAttributeValue):
613            raise TypeError("Expecting %r type; got: %r" % 
614                            (XSGroupRoleAttributeValue, type(attributeValue)))
615           
616        ElementTree._namespace_map[attributeValue.namespaceURI
617                                   ] = attributeValue.namespacePrefix
618       
619        elem.set(cls.GROUP_ATTRIB_NAME, attributeValue.group)
620        elem.set(cls.ROLE_ATTRIB_NAME, attributeValue.role)
621
622        return elem
623
624
625class AttributeValueElementTreeFactory(object):
626    """Class factory for AttributeValue ElementTree classes.  These classes are
627    used to represent SAML Attribute value types
628   
629    @type classMap: dict
630    @cvar classMap: mapping between SAML AttributeValue class and its
631    ElementTree handler class
632    @type idMap: dict
633    @cvar idMap: mapping between SAML AttributeValue string identifier and
634    its ElementTree handler class
635    """
636    classMap = {
637        XSStringAttributeValue: XSStringAttributeValueElementTree
638    }
639   
640    idMap = {
641        "xs:string": XSStringAttributeValueElementTree
642    }
643   
644    def __init__(self, customClassMap={}, customIdMap={}): 
645        """Set-up a SAML class to ElementTree mapping
646        @type customClassMap: dict
647        @param customClassMap: mapping for custom SAML AttributeValue classes
648        to their respective ElementTree based representations.  This appends
649        to self.__classMap
650        @type customIdMap: dict
651        @param customIdMap: string ID based mapping for custom SAML
652        AttributeValue classes to their respective ElementTree based
653        representations.  As with customClassMap, this appends to
654        to the respective self.__idMap
655        """
656        self.__classMap = AttributeValueElementTreeFactory.classMap
657        for samlClass, etreeClass in customClassMap.items(): 
658            if not issubclass(samlClass, AttributeValue):
659                raise TypeError("Input custom class must be derived from %r, "
660                                "got %r instead" % (Attribute, samlClass))
661               
662            self.__classMap[samlClass] = etreeClass
663
664        self.__idMap = AttributeValueElementTreeFactory.idMap
665        for samlId, etreeClass in customIdMap.items(): 
666            if not isinstance(samlId, basestring):
667                raise TypeError("Input custom SAML identifier must be a "
668                                "string, got %r instead" % samlId)
669               
670            self.__idMap[samlId] = etreeClass
671           
672    def __call__(self, input):
673        """Create an ElementTree object based on the Attribute class type
674        passed in
675       
676        @type input: ndg.security.common.saml.AttributeValue or basestring
677        @param input: pass an AttributeValue derived type or a string.  If
678        an AttributeValue type, then self.__classMap is checked for a matching
679        AttributeValue class entry, if a string is passed, self.__idMap is
680        checked for a matching string ID.  In both cases, if a match is
681        found an ElementTree class is returned which can render or parse
682        the relevant AttributeValue class
683        """
684        if isinstance(input, AttributeValue):
685            xmlObjectClass = self.__classMap.get(input.__class__)
686            if xmlObjectClass is None:
687                raise TypeError("no matching XMLObject class representation "
688                                "for SAML class %r" % input.__class__)
689               
690        elif isinstance(input, basestring):
691            xmlObjectClass = self.__idMap.get(input)
692            if xmlObjectClass is None:
693                raise TypeError("no matching XMLObject class representation "
694                                "for SAML AttributeValue type %s" % input)
695        else:
696            raise TypeError("Expecting %r class got %r" % (AttributeValue, 
697                                                           type(input)))
698           
699           
700        return xmlObjectClass
701
702       
703class IssuerElementTree(Issuer):
704    """Represent a SAML Issuer element in XML using ElementTree"""
705   
706    @classmethod
707    def create(cls, issuer):
708        """Create an XML representation of the input SAML issuer object"""
709        if not isinstance(issuer, Issuer):
710            raise TypeError("Expecting %r class got %r" % (Issuer, 
711                                                           type(issuer)))
712        attrib = {
713            cls.FORMAT_ATTRIB_NAME: issuer.format
714        }
715        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
716        elem = ElementTree.Element(tag, **attrib)
717        ElementTree._namespace_map[issuer.qname.namespaceURI
718                                   ] = issuer.qname.prefix
719                                   
720        elem.text = issuer.value
721
722        return elem
723
724    @classmethod
725    def parse(cls, elem):
726        """Parse ElementTree element into a SAML Issuer instance"""
727        if not ElementTree.iselement(elem):
728            raise TypeError("Expecting %r input type for parsing; got %r" %
729                            (ElementTree.Element, elem))
730
731        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
732            raise XMLObjectParseError('No "%s" element found' %
733                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
734           
735        issuerFormat = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
736        if issuerFormat is None:
737            raise XMLObjectParseError('No "%s" attribute found in "%s" '
738                                      'element' %
739                                      (issuerFormat,
740                                       cls.DEFAULT_ELEMENT_LOCAL_NAME))
741        issuer = Issuer()
742        issuer.format = issuerFormat
743        issuer.value = elem.text.strip() 
744       
745        return issuer
746
747       
748class NameIdElementTree(NameID):
749    """Represent a SAML Name Identifier in XML using ElementTree"""
750   
751    @classmethod
752    def create(cls, nameID):
753        """Create an XML representation of the input SAML Name Identifier
754        object
755        @type nameID: ndg.security.common.saml.Subject
756        @param nameID: SAML subject
757        @rtype: ElementTree.Element
758        @return: Name ID as ElementTree XML element"""
759       
760        if not isinstance(nameID, NameID):
761            raise TypeError("Expecting %r class got %r" % (NameID, 
762                                                           type(nameID)))
763        attrib = {
764            cls.FORMAT_ATTRIB_NAME: nameID.format
765        }
766        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
767        elem = ElementTree.Element(tag, **attrib)
768       
769        ElementTree._namespace_map[nameID.qname.namespaceURI
770                                   ] = nameID.qname.prefix
771       
772        elem.text = nameID.value
773
774        return elem
775
776    @classmethod
777    def parse(cls, elem):
778        """Parse ElementTree element into a SAML NameID object
779       
780        @type elem: ElementTree.Element
781        @param elem: Name ID as ElementTree XML element
782        @rtype: ndg.security.common.saml.NameID
783        @return: SAML Name ID
784        """
785        if not ElementTree.iselement(elem):
786            raise TypeError("Expecting %r input type for parsing; got %r" %
787                            (ElementTree.Element, elem))
788
789        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
790            raise XMLObjectParseError("No \"%s\" element found" %
791                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
792           
793        format = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
794        if format is None:
795            raise XMLObjectParseError('No "%s" attribute found in "%s" '
796                                      'element' %
797                                      (format,
798                                       cls.DEFAULT_ELEMENT_LOCAL_NAME))
799        nameID = NameID()
800        nameID.format = format
801        nameID.value = elem.text.strip() 
802       
803        return nameID
804
805
806class SubjectElementTree(Subject):
807    """Represent a SAML Subject in XML using ElementTree"""
808   
809    @classmethod
810    def create(cls, subject):
811        """Create an XML representation of the input SAML subject object
812        @type subject: ndg.security.common.saml.Subject
813        @param subject: SAML subject
814        @rtype: ElementTree.Element
815        @return: subject as ElementTree XML element
816        """
817        if not isinstance(subject, Subject):
818            raise TypeError("Expecting %r class got %r" % (Subject, 
819                                                           type(subject)))
820           
821        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
822        elem = ElementTree.Element(tag)
823       
824        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
825                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
826
827           
828        nameIdElem = NameIdElementTree.create(subject.nameID)
829        elem.append(nameIdElem)
830       
831        return elem
832
833    @classmethod
834    def parse(cls, elem):
835        """Parse ElementTree element into a SAML Subject object
836       
837        @type elem: ElementTree.Element
838        @param elem: subject as ElementTree XML element
839        @rtype: ndg.security.common.saml.Subject
840        @return: SAML subject
841        """
842        if not ElementTree.iselement(elem):
843            raise TypeError("Expecting %r input type for parsing; got %r" %
844                            (ElementTree.Element, elem))
845
846        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
847            raise XMLObjectParseError("No \"%s\" element found" %
848                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
849           
850        if len(elem) != 1:
851            raise XMLObjectParseError("Expecting single Name ID child element "
852                                      "for SAML Subject element")
853           
854        subject = Subject()
855        subject.nameID = NameIdElementTree.parse(elem[0])
856       
857        return subject
858
859       
860class StatusCodeElementTree(StatusCode):
861    """Represent a SAML Name Identifier in XML using ElementTree"""
862   
863    @classmethod
864    def create(cls, statusCode):
865        """Create an XML representation of the input SAML Name Status Code
866       
867        @type statusCode: ndg.security.common.saml.StatusCode
868        @param statusCode: SAML Status Code
869        @rtype: ElementTree.Element
870        @return: Status Code as ElementTree XML element"""
871       
872        if not isinstance(statusCode, StatusCode):
873            raise TypeError("Expecting %r class got %r" % (StatusCode, 
874                                                           type(statusCode)))
875           
876        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
877        elem = ElementTree.Element(tag)
878       
879        ElementTree._namespace_map[statusCode.qname.namespaceURI
880                                   ] = statusCode.qname.prefix
881       
882        elem.text = statusCode.value
883
884        return elem
885
886    @classmethod
887    def parse(cls, elem):
888        """Parse ElementTree element into a SAML StatusCode object
889       
890        @type elem: ElementTree.Element
891        @param elem: Status Code as ElementTree XML element
892        @rtype: ndg.security.common.saml.StatusCode
893        @return: SAML Status Code
894        """
895        if not ElementTree.iselement(elem):
896            raise TypeError("Expecting %r input type for parsing; got %r" %
897                            (ElementTree.Element, elem))
898
899        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
900            raise XMLObjectParseError('No "%s" element found' %
901                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
902           
903        statusCode = StatusCode()
904        if elem.text is not None:
905            statusCode.value = elem.text.strip() 
906       
907        return statusCode
908
909
910class StatusElementTree(Status):
911    """Represent a SAML Status in XML using ElementTree"""
912   
913    @classmethod
914    def create(cls, status):
915        """Create an XML representation of the input SAML subject object
916        @type subject: ndg.security.common.saml.Status
917        @param subject: SAML subject
918        @rtype: ElementTree.Element
919        @return: subject as ElementTree XML element
920        """
921        if not isinstance(status, Status):
922            raise TypeError("Expecting %r class got %r" % (status, 
923                                                           type(Status)))
924           
925        tag = str(QName.fromGeneric(Status.DEFAULT_ELEMENT_NAME)) 
926        elem = ElementTree.Element(tag)
927       
928        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
929                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
930       
931        statusCodeElem = StatusCodeElementTree.create(status.statusCode)
932        elem.append(statusCodeElem)
933       
934        return elem
935
936    @classmethod
937    def parse(cls, elem):
938        """Parse ElementTree element into a SAML Status object
939       
940        @type elem: ElementTree.Element
941        @param elem: subject as ElementTree XML element
942        @rtype: ndg.security.common.saml.Status
943        @return: SAML subject
944        """
945        if not ElementTree.iselement(elem):
946            raise TypeError("Expecting %r input type for parsing; got %r" %
947                            (ElementTree.Element, elem))
948
949        if QName.getLocalPart(elem.tag) != Status.DEFAULT_ELEMENT_LOCAL_NAME:
950            raise XMLObjectParseError('No "%s" element found' %
951                                      Status.DEFAULT_ELEMENT_LOCAL_NAME)
952           
953        if len(elem) != 1:
954            raise XMLObjectParseError("Expecting single StatusCode child "
955                                      "element for SAML Status element")
956           
957        status = Status()
958        status.statusCode = StatusCodeElementTree.parse(elem[0])
959       
960        return status
961   
962   
963class AttributeQueryElementTree(AttributeQuery):
964    """Represent a SAML Attribute Query in XML using ElementTree"""
965       
966    @classmethod
967    def create(cls, 
968               attributeQuery, 
969               **attributeValueElementTreeFactoryKw):
970        """Create an XML representation of the input SAML Attribute Query
971        object
972
973        @type attributeQuery: ndg.security.common.saml.AttributeQuery
974        @param attributeQuery: SAML Attribute Query
975        @rtype: ElementTree.Element
976        @return: Attribute Query as ElementTree XML element
977        """
978        if not isinstance(attributeQuery, AttributeQuery):
979            raise TypeError("Expecting %r class got %r" % (AttributeQuery, 
980                                                        type(attributeQuery)))
981           
982       
983        issueInstant = SAMLDateTime.toString(attributeQuery.issueInstant)
984        attrib = {
985            cls.ID_ATTRIB_NAME: attributeQuery.id,
986            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
987           
988            # Nb. Version is a SAMLVersion instance and requires explicit cast
989            cls.VERSION_ATTRIB_NAME: str(attributeQuery.version)
990        }
991       
992        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
993        elem = ElementTree.Element(tag, **attrib)
994       
995        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
996                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
997       
998        issuerElem = IssuerElementTree.create(attributeQuery.issuer)
999        elem.append(issuerElem)
1000
1001        subjectElem = SubjectElementTree.create(attributeQuery.subject)
1002        elem.append(subjectElem)
1003
1004        for attribute in attributeQuery.attributes:
1005            # Factory enables support for multiple attribute types
1006            attributeElem = AttributeElementTree.create(attribute,
1007                                        **attributeValueElementTreeFactoryKw)
1008            elem.append(attributeElem)
1009       
1010        return elem
1011
1012    @classmethod
1013    def parse(cls, elem):
1014        """Parse ElementTree element into a SAML AttributeQuery object
1015       
1016        @type elem: ElementTree.Element
1017        @param elem: XML element containing the AttributeQuery
1018        @rtype: ndg.security.common.saml.AttributeQuery
1019        @return: AttributeQuery object
1020        """
1021        if not ElementTree.iselement(elem):
1022            raise TypeError("Expecting %r input type for parsing; got %r" %
1023                            (ElementTree.Element, elem))
1024
1025        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1026            raise XMLObjectParseError("No \"%s\" element found" %
1027                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1028       
1029        # Unpack attributes from top-level element
1030        attributeValues = []
1031        for attributeName in (cls.VERSION_ATTRIB_NAME,
1032                              cls.ISSUE_INSTANT_ATTRIB_NAME,
1033                              cls.ID_ATTRIB_NAME):
1034            attributeValue = elem.attrib.get(attributeName)
1035            if attributeValue is None:
1036                raise XMLObjectParseError('No "%s" attribute found in "%s" '
1037                                 'element' %
1038                                 (attributeName,
1039                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
1040               
1041            attributeValues.append(attributeValue)
1042       
1043        attributeQuery = AttributeQuery()
1044        attributeQuery.version = SAMLVersion(attributeValues[0])
1045        if attributeQuery.version != SAMLVersion.VERSION_20:
1046            raise NotImplementedError("Parsing for %r is implemented for "
1047                                      "SAML version %s only; version %s is " 
1048                                      "not supported" % 
1049                                      (cls,
1050                                       SAMLVersion(SAMLVersion.VERSION_20),
1051                                       SAMLVersion(attributeQuery.version)))
1052           
1053        attributeQuery.issueInstant = SAMLDateTime.fromString(
1054                                                            attributeValues[1])
1055        attributeQuery.id = attributeValues[2]
1056       
1057        for childElem in elem:
1058            localName = QName.getLocalPart(childElem.tag)
1059            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1060                # Parse Issuer
1061                attributeQuery.issuer = IssuerElementTree.parse(childElem)
1062               
1063            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1064                # Parse Subject
1065                attributeQuery.subject = SubjectElementTree.parse(childElem)
1066           
1067            elif localName == Attribute.DEFAULT_ELEMENT_LOCAL_NAME:
1068                attribute = AttributeElementTree.parse(childElem)
1069                attributeQuery.attributes.append(attribute)
1070            else:
1071                raise XMLObjectParseError("Unrecognised AttributeQuery child "
1072                                          "element \"%s\"" % localName)
1073       
1074        return attributeQuery
1075       
1076   
1077class ResponseElementTree(Response):
1078    """Represent a SAML Response in XML using ElementTree"""
1079       
1080    @classmethod
1081    def create(cls, 
1082               response, 
1083               **attributeValueElementTreeFactoryKw):
1084        """Create an XML representation of the input SAML Response
1085        object
1086
1087        @type response: ndg.security.common.saml.Response
1088        @param response: SAML Response
1089        @rtype: ElementTree.Element
1090        @return: Response as ElementTree XML element
1091        """
1092        if not isinstance(response, Response):
1093            raise TypeError("Expecting %r class, got %r" % (Response, 
1094                                                            type(response)))
1095           
1096       
1097        issueInstant = SAMLDateTime.toString(response.issueInstant)
1098        attrib = {
1099            cls.ID_ATTRIB_NAME: response.id,
1100            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1101            cls.IN_RESPONSE_TO_ATTRIB_NAME: response.inResponseTo,
1102           
1103            # Nb. Version is a SAMLVersion instance and requires explicit cast
1104            cls.VERSION_ATTRIB_NAME: str(response.version)
1105        }
1106       
1107        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))       
1108        elem = ElementTree.Element(tag, **attrib)
1109       
1110        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1111                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1112           
1113        issuerElem = IssuerElementTree.create(response.issuer)
1114        elem.append(issuerElem)
1115
1116        statusElem = StatusElementTree.create(response.status)       
1117        elem.append(statusElem)
1118
1119        for assertion in response.assertions:
1120            # Factory enables support for multiple attribute types
1121            assertionElem = AssertionElementTree.create(assertion,
1122                                        **attributeValueElementTreeFactoryKw)
1123            elem.append(assertionElem)
1124       
1125        return elem
1126
1127    @classmethod
1128    def parse(cls, elem):
1129        """Parse ElementTree element into a SAML Response object
1130       
1131        @type elem: ElementTree.Element
1132        @param elem: XML element containing the Response
1133        @rtype: ndg.security.common.saml.Response
1134        @return: Response object
1135        """
1136        if not ElementTree.iselement(elem):
1137            raise TypeError("Expecting %r input type for parsing; got %r" %
1138                            (ElementTree.Element, elem))
1139
1140        if QName.getLocalPart(elem.tag) != Response.DEFAULT_ELEMENT_LOCAL_NAME:
1141            raise XMLObjectParseError("No \"%s\" element found" %
1142                                      Response.DEFAULT_ELEMENT_LOCAL_NAME)
1143       
1144        # Unpack attributes from top-level element
1145        attributeValues = []
1146        for attributeName in (Response.VERSION_ATTRIB_NAME,
1147                              Response.ISSUE_INSTANT_ATTRIB_NAME,
1148                              Response.ID_ATTRIB_NAME,
1149                              Response.IN_RESPONSE_TO_ATTRIB_NAME):
1150            attributeValue = elem.attrib.get(attributeName)
1151            if attributeValue is None:
1152                raise XMLObjectParseError('No "%s" attribute found in "%s" '
1153                                          'element' %
1154                                         (attributeName,
1155                                          Response.DEFAULT_ELEMENT_LOCAL_NAME))
1156               
1157            attributeValues.append(attributeValue)
1158       
1159        response = Response()
1160        response.version = SAMLVersion(attributeValues[0])
1161        if response.version != SAMLVersion.VERSION_20:
1162            raise NotImplementedError("Parsing for %r is implemented for "
1163                                      "SAML version %s only; version %s is " 
1164                                      "not supported" % 
1165                                      (cls,
1166                                       SAMLVersion(SAMLVersion.VERSION_20),
1167                                       SAMLVersion(response.version)))
1168           
1169        response.issueInstant = SAMLDateTime.fromString(attributeValues[1])
1170        response.id = attributeValues[2]
1171        response.inResponseTo = attributeValues[3]
1172       
1173        for childElem in elem:
1174            localName = QName.getLocalPart(childElem.tag)
1175            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1176                # Parse Issuer
1177                response.issuer = IssuerElementTree.parse(childElem)
1178           
1179            elif localName == Status.DEFAULT_ELEMENT_LOCAL_NAME:
1180                # Get status of response
1181                response.status = StatusElementTree.parse(childElem)
1182               
1183            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1184                # Parse Subject
1185                response.subject = SubjectElementTree.parse(childElem)
1186           
1187            elif localName == Assertion.DEFAULT_ELEMENT_LOCAL_NAME:
1188                response.assertions.append(
1189                                        AssertionElementTree.parse(childElem))
1190            else:
1191                raise XMLObjectParseError('Unrecognised Response child '
1192                                          'element "%s"' % localName)
1193       
1194        return response
1195
Note: See TracBrowser for help on using the repository browser.