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

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

Assertion parsing near complete but fixes needed to XSGroupRoleElementTree

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    AuthnStatement, AuthzDecisionStatement, \
42    Assertion, Conditions, AttributeValue, AttributeQuery, Subject, NameID, \
43    Issuer, SAMLVersion, Response, Status, StatusCode, Advice, \
44    XSStringAttributeValue, XSGroupRoleAttributeValue
45from saml.common.xml import SAMLConstants
46from saml.xml import XMLTypeParseError
47from saml.utils import SAMLDateTime
48from saml.xml import QName as GenericQName
49
50
51# Generic ElementTree Helper classes
52class QName(ElementTree.QName):
53    """Extend ElementTree implementation for improved attribute access support
54    """ 
55
56    # ElementTree tag is of the form {namespace}localPart.  getNs extracts the
57    # namespace from within the brackets but if not found returns ''
58    getNs = staticmethod(lambda tag: getattr(re.search('(?<=\{).+(?=\})', tag),
59                                             'group', 
60                                             str)())
61                                             
62    getLocalPart = staticmethod(lambda tag: tag.rsplit('}', 1)[-1])
63
64    def __init__(self, input, tag=None, prefix=None):
65        """
66        @type input: basestring
67        @param input: ElementTree style namespace URI + tag name -
68        {namespace URI}tag - OR if tag keyword is set, the namespace URI alone
69        @type tag: basestring / None
70        @param tag: element tag name.  If None, input must contain the
71        namespace URI and tag name in the ElementTree form {namespace URI}tag.
72        @type prefix: basestring / None
73        @param prefix: namespace prefix
74        """
75       
76        ElementTree.QName.__init__(self, input, tag=tag)
77       
78        if tag:
79            self.namespaceURI = input
80            self.localPart = tag
81        else:
82            # No tag provided namespace and localPart of QN must be parsed from
83            # the namespace
84            self.namespaceURI = QName.getNs(input)
85            self.localPart = QName.getLocalPart(input)
86           
87        self.prefix = prefix
88   
89    def _getPrefix(self):
90        return self.__prefix
91
92    def _setPrefix(self, value):
93        self.__prefix = value
94   
95    prefix = property(_getPrefix, _setPrefix, None, "Prefix")
96
97    def _getLocalPart(self):
98        return self.__localPart
99   
100    def _setLocalPart(self, value):
101        self.__localPart = value
102       
103    localPart = property(_getLocalPart, _setLocalPart, None, "LocalPart")
104
105    def _getNamespaceURI(self):
106        return self.__namespaceURI
107
108    def _setNamespaceURI(self, value):
109        self.__namespaceURI = value
110 
111    namespaceURI = property(_getNamespaceURI, _setNamespaceURI, None, 
112                            "Namespace URI'")
113
114    @classmethod
115    def fromGeneric(cls, genericQName):
116        '''Cast the generic QName type in saml.xml to the ElementTree specific
117        implementation'''
118        if not isinstance(genericQName, GenericQName):
119            raise TypeError("Expecting %r for QName, got %r" % (GenericQName,
120                                                        type(genericQName)))
121           
122        qname = cls(genericQName.namespaceURI, 
123                    tag=genericQName.localPart,
124                    prefix=genericQName.prefix)
125        return qname
126   
127   
128def prettyPrint(*arg, **kw):
129    '''Lightweight pretty printing of ElementTree elements'''
130   
131    # Keep track of namespace declarations made so they're not repeated
132    declaredNss = []
133   
134    _prettyPrint = PrettyPrint(declaredNss)
135    return _prettyPrint(*arg, **kw)
136
137
138class PrettyPrint(object):
139    def __init__(self, declaredNss):
140        self.declaredNss = declaredNss
141   
142    @staticmethod
143    def estrip(elem):
144        ''' Just want to get rid of unwanted whitespace '''
145        if elem is None:
146            return ''
147        else:
148            # just in case the elem is another simple type - e.g. int -
149            # wrapper it as a string
150            return str(elem).strip()
151       
152    def __call__(self, elem, indent='', html=0, space=' '*4):
153        '''Most of the work done in this wrapped function - wrapped so that
154        state can be maintained for declared namespace declarations during
155        recursive calls using "declaredNss" above''' 
156        strAttribs = []
157        for attr, attrVal in elem.attrib.items():
158            nsDeclaration = ''
159           
160            attrNamespace = QName.getNs(attr)
161            if attrNamespace:
162                nsPrefix = ElementTree._namespace_map.get(attrNamespace)
163                if nsPrefix is None:
164                    raise KeyError('prettyPrint: missing namespace "%s" for ' 
165                                   'ElementTree._namespace_map'%attrNamespace)
166               
167                attr = "%s:%s" % (nsPrefix, QName.getLocalPart(attr))
168               
169                if attrNamespace not in self.declaredNss:
170                    nsDeclaration = ' xmlns:%s="%s"' % (nsPrefix,attrNamespace)
171                    self.declaredNss.append(attrNamespace)
172               
173            strAttribs.append('%s %s="%s"' % (nsDeclaration, attr, attrVal))
174           
175        strAttrib = ''.join(strAttribs)
176       
177        namespace = QName.getNs(elem.tag)
178        nsPrefix = ElementTree._namespace_map.get(namespace)
179        if nsPrefix is None:
180            raise KeyError('prettyPrint: missing namespace "%s" for ' 
181                           'ElementTree._namespace_map' % namespace)
182           
183        tag = "%s:%s" % (nsPrefix, QName.getLocalPart(elem.tag))
184       
185        # Put in namespace declaration if one doesn't already exist
186        # FIXME: namespace declaration handling is wrong for handling child
187        # element scope
188        if namespace in self.declaredNss:
189            nsDeclaration = ''
190        else:
191            nsDeclaration = ' xmlns:%s="%s"' % (nsPrefix, namespace)
192            self.declaredNss.append(namespace)
193           
194        result = '%s<%s%s%s>%s' % (indent, tag, nsDeclaration, strAttrib, 
195                                   PrettyPrint.estrip(elem.text))
196       
197        children = len(elem)
198        if children:
199            for child in elem:
200                declaredNss = self.declaredNss[:]
201                _prettyPrint = PrettyPrint(declaredNss)
202                result += '\n'+ _prettyPrint(child, indent=indent+space) 
203               
204            result += '\n%s%s</%s>' % (indent,
205                                       PrettyPrint.estrip(child.tail),
206                                       tag)
207        else:
208            result += '</%s>' % tag
209           
210        return result
211
212
213# ElementTree SAML wrapper classes
214class ConditionsElementTree(Conditions):
215    """ElementTree based XML representation of Conditions class
216    """
217   
218    @classmethod
219    def toXML(cls, conditions):
220        """Make a tree of a XML elements based on the assertion conditions
221       
222        @type assertion: saml.saml2.core.Conditions
223        @param assertion: Assertion conditions to be represented as an
224        ElementTree Element
225        @rtype: ElementTree.Element
226        @return: ElementTree Element
227        """
228        if not isinstance(conditions, Conditions):
229            raise TypeError("Expecting %r type got: %r" % (Conditions,
230                                                           conditions))
231       
232        notBeforeStr = SAMLDateTime.toString(conditions.notBefore)
233        notOnOrAfterStr = SAMLDateTime.toString(conditions.notOnOrAfter)
234        attrib = {
235            cls.NOT_BEFORE_ATTRIB_NAME: notBeforeStr,
236            cls.NOT_ON_OR_AFTER_ATTRIB_NAME: notOnOrAfterStr,
237        }
238       
239        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
240        elem = ElementTree.Element(tag, **attrib)
241       
242        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
243                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
244
245        for condition in conditions.conditions:
246            raise NotImplementedError("Conditions list creation is not "
247                                      "implemented")
248               
249        return elem
250   
251    @classmethod
252    def fromXML(cls, elem):
253        """Parse an ElementTree SAML Conditions element into an
254        Conditions object
255       
256        @type elem: ElementTree.Element
257        @param elem: ElementTree element containing the conditions
258        @rtype: saml.saml2.core.Conditions
259        @return: Conditions object"""
260       
261        if not ElementTree.iselement(elem):
262            raise TypeError("Expecting %r input type for parsing; got %r" %
263                            (ElementTree.Element, elem))
264
265        localName = QName.getLocalPart(elem.tag)
266        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
267            raise XMLTypeParseError("No \"%s\" element found" %
268                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
269       
270       
271        if len(elem) > 0:
272            raise NotImplementedError("Conditions list parsing is not "
273                                      "implemented")
274
275        conditions = Conditions()
276        notBefore = elem.attrib.get(Conditions.NOT_BEFORE_ATTRIB_NAME)
277        if notBefore is not None:
278            conditions.notBefore = SAMLDateTime.fromString(notBefore)
279           
280        notOnOrAfter = elem.attrib.get(Conditions.NOT_ON_OR_AFTER_ATTRIB_NAME)
281        if notBefore is not None:
282            conditions.notOnOrAfter = SAMLDateTime.fromString(notOnOrAfter)
283           
284        return conditions               
285       
286               
287class AssertionElementTree(Assertion):
288    """ElementTree based XML representation of Assertion class
289    """
290   
291    @classmethod
292    def toXML(cls, assertion, **attributeValueElementTreeFactoryKw):
293        """Make a tree of a XML elements based on the assertion
294       
295        @type assertion: saml.saml2.core.Assertion
296        @param assertion: Assertion to be represented as an ElementTree Element
297        @type attributeValueElementTreeFactoryKw: dict
298        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
299        factory
300        @rtype: ElementTree.Element
301        @return: ElementTree Element
302        """
303       
304        if not isinstance(assertion, Assertion):
305            raise TypeError("Expecting %r type got: %r"%(Assertion, assertion))
306       
307        issueInstant = SAMLDateTime.toString(assertion.issueInstant)
308        attrib = {
309            cls.ID_ATTRIB_NAME: assertion.id,
310            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
311           
312            # Nb. Version is a SAMLVersion instance and requires explicit cast
313            cls.VERSION_ATTRIB_NAME: str(assertion.version)
314        }
315        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
316        elem = ElementTree.Element(tag, **attrib)
317       
318        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
319                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
320       
321        if assertion.issuer is not None:
322            issuerElem = IssuerElementTree.toXML(assertion.issuer)
323            elem.append(issuerElem)
324       
325        if assertion.subject is not None:
326            subjectElem = SubjectElementTree.toXML(assertion.subject)
327            elem.append(subjectElem)
328
329        if assertion.advice:
330            raise NotImplementedError("Assertion Advice creation is not "
331                                      "implemented")
332
333        if assertion.conditions is not None:
334            conditionsElem = ConditionsElementTree.toXML(assertion.conditions)
335            elem.append(conditionsElem)
336           
337        for statement in assertion.statements:
338            raise NotImplementedError("Assertion Statement creation is not "
339                                      "implemented")
340       
341        for authnStatement in assertion.authnStatements:
342            raise NotImplementedError("Assertion Authentication Statement "
343                                      "creation is not implemented")
344       
345        for authzDecisionStatement in assertion.authzDecisionStatements:
346            raise NotImplementedError("Assertion Authorisation Decision "
347                                      "Statement creation is not implemented")
348           
349        for attributeStatement in assertion.attributeStatements:
350            attributeStatementElem = AttributeStatementElementTree.toXML(
351                                        attributeStatement,
352                                        **attributeValueElementTreeFactoryKw)
353            elem.append(attributeStatementElem)
354       
355        return elem
356
357    @classmethod
358    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
359        """Parse an ElementTree representation of an Assertion into an
360        Assertion object
361       
362        @type elem: ElementTree.Element
363        @param elem: ElementTree element containing the assertion
364        @rtype: saml.saml2.core.Assertion
365        @return: Assertion object"""
366        if not ElementTree.iselement(elem):
367            raise TypeError("Expecting %r input type for parsing; got %r" %
368                            (ElementTree.Element, elem))
369
370        localName = QName.getLocalPart(elem.tag)
371        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
372            raise XMLTypeParseError("No \"%s\" element found" %
373                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
374       
375       
376        # Unpack attributes from top-level element
377        attributeValues = []
378        for attributeName in (cls.VERSION_ATTRIB_NAME,
379                              cls.ISSUE_INSTANT_ATTRIB_NAME,
380                              cls.ID_ATTRIB_NAME):
381            attributeValue = elem.attrib.get(attributeName)
382            if attributeValue is None:
383                raise XMLTypeParseError('No "%s" attribute found in "%s" '
384                                          'element' %
385                                          (attributeName,
386                                           cls.DEFAULT_ELEMENT_LOCAL_NAME))
387               
388            attributeValues.append(attributeValue)
389       
390        assertion = Assertion()
391        assertion.version = SAMLVersion(attributeValues[0])
392        if assertion.version != SAMLVersion.VERSION_20:
393            raise NotImplementedError("Parsing for %r is implemented for "
394                                      "SAML version %s only; version %s is " 
395                                      "not supported" % 
396                                      (cls,
397                                       SAMLVersion(SAMLVersion.VERSION_20),
398                                       SAMLVersion(assertion.version)))
399           
400        assertion.issueInstant = SAMLDateTime.fromString(attributeValues[1])
401        assertion.id = attributeValues[2]
402       
403        for childElem in elem:
404            localName = QName.getLocalPart(childElem.tag)
405            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
406                # Parse Issuer
407                assertion.issuer = IssuerElementTree.fromXML(childElem)
408               
409            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
410                # Parse subject
411                assertion.subject = SubjectElementTree.fromXML(childElem)
412               
413            elif localName == Advice.DEFAULT_ELEMENT_LOCAL_NAME:
414                raise NotImplementedError("Assertion Advice parsing is not "
415                                          "implemented")
416               
417            elif localName == Conditions.DEFAULT_ELEMENT_LOCAL_NAME:
418                assertion.conditions = ConditionsElementTree.fromXML(childElem)
419       
420            elif localName == AuthnStatement.DEFAULT_ELEMENT_LOCAL_NAME:
421                raise NotImplementedError("Assertion Authentication Statement "
422                                          "parsing is not implemented")
423       
424            elif localName==AuthzDecisionStatement.DEFAULT_ELEMENT_LOCAL_NAME:
425                raise NotImplementedError("Assertion Authorisation Decision "
426                                          "Statement parsing is not "
427                                          "implemented")
428           
429            elif localName == AttributeStatement.DEFAULT_ELEMENT_LOCAL_NAME:
430                attributeStatement = AttributeStatementElementTree.fromXML(
431                                        childElem,
432                                        **attributeValueElementTreeFactoryKw)
433                assertion.attributeStatements.append(attributeStatement)
434            else:
435                raise XMLTypeParseError('Assertion child element name "%s" '
436                                          'not recognised' % localName)
437       
438        return assertion
439
440 
441class AttributeStatementElementTree(AttributeStatement):
442    """ElementTree XML representation of AttributeStatement"""
443   
444    @classmethod
445    def toXML(cls, attributeStatement, **attributeValueElementTreeFactoryKw):
446        """Make a tree of a XML elements based on the attribute statement
447       
448        @type assertion: saml.saml2.core.AttributeStatement
449        @param assertion: Attribute Statement to be represented as an
450        ElementTree Element
451        @type attributeValueElementTreeFactoryKw: dict
452        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
453        factory
454        @rtype: ElementTree.Element
455        @return: ElementTree Element
456        """
457        if not isinstance(attributeStatement, AttributeStatement):
458            raise TypeError("Expecting %r type got: %r" % (AttributeStatement, 
459                                                           attributeStatement))
460           
461        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
462        elem = ElementTree.Element(tag)
463        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
464                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
465
466        for attribute in attributeStatement.attributes:
467            # Factory enables support for multiple attribute types
468            attributeElem = AttributeElementTree.toXML(attribute,
469                                        **attributeValueElementTreeFactoryKw)
470            elem.append(attributeElem)
471       
472        return elem
473   
474    @classmethod
475    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
476        """Parse an ElementTree SAML AttributeStatement element into an
477        AttributeStatement object
478       
479        @type elem: ElementTree.Element
480        @param elem: ElementTree element containing the AttributeStatement
481        @type attributeValueElementTreeFactoryKw: dict
482        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
483        factory
484        @rtype: saml.saml2.core.AttributeStatement
485        @return: Attribute Statement"""
486       
487        if not ElementTree.iselement(elem):
488            raise TypeError("Expecting %r input type for parsing; got %r" %
489                            (ElementTree.Element, elem))
490
491        localName = QName.getLocalPart(elem.tag)
492        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
493            raise XMLTypeParseError("No \"%s\" element found" %
494                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
495       
496       
497        attributeStatement = AttributeStatement()
498
499        for childElem in elem:
500            # Factory enables support for multiple attribute types
501            attribute = AttributeElementTree.fromXML(childElem,
502                                        **attributeValueElementTreeFactoryKw)
503            attributeStatement.attributes.append(attribute)
504       
505        return attributeStatement
506
507
508class AttributeElementTree(Attribute):
509    """ElementTree XML representation of SAML Attribute object.  Extend
510    to make Attribute types""" 
511
512    @classmethod
513    def toXML(cls, attribute, **attributeValueElementTreeFactoryKw):
514        """Make a tree of a XML elements based on the Attribute
515       
516        @type assertion: saml.saml2.core.Attribute
517        @param assertion: Attribute to be represented as an ElementTree Element
518        @type attributeValueElementTreeFactoryKw: dict
519        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
520        factory
521        @rtype: ElementTree.Element
522        @return: ElementTree Element
523        """
524        if not isinstance(attribute, Attribute):
525            raise TypeError("Expecting %r type got: %r"%(Attribute, attribute))
526       
527        tag = str(QName.fromGeneric(Attribute.DEFAULT_ELEMENT_NAME))   
528        elem = ElementTree.Element(tag)
529        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
530                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
531       
532        if attribute.friendlyName:
533            elem.set(cls.FRIENDLY_NAME_ATTRIB_NAME, attribute.friendlyName) 
534             
535        if attribute.name:
536            elem.set(cls.NAME_ATTRIB_NAME, attribute.name)
537       
538        if attribute.nameFormat:
539            elem.set(cls.NAME_FORMAT_ATTRIB_NAME, attribute.nameFormat)
540
541        for attributeValue in attribute.attributeValues:
542            factory = AttributeValueElementTreeFactory(
543                                        **attributeValueElementTreeFactoryKw)
544           
545            attributeValueElementTree = factory(attributeValue)
546           
547            attributeValueElem=attributeValueElementTree.toXML(attributeValue)
548            elem.append(attributeValueElem)
549           
550        return elem
551 
552    @classmethod
553    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
554        """Parse ElementTree element into a SAML Attribute object
555       
556        @type elem: ElementTree.Element
557        @param elem: Attribute as ElementTree XML element
558        @type attributeValueElementTreeFactoryKw: dict
559        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
560        factory
561        @rtype: saml.saml2.core.Attribute
562        @return: SAML Attribute
563        """
564        if not ElementTree.iselement(elem):
565            raise TypeError("Expecting %r input type for parsing; got %r" %
566                            (ElementTree.Element, elem))
567
568        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
569            raise XMLTypeParseError("No \"%s\" element found" %
570                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
571           
572        attribute = Attribute()
573           
574        name = elem.attrib.get(cls.NAME_ATTRIB_NAME)
575        if name is not None:
576            attribute.name = name
577           
578        friendlyName = elem.attrib.get(cls.FRIENDLY_NAME_ATTRIB_NAME)
579        if friendlyName is not None:
580            attribute.friendlyName = friendlyName
581           
582        nameFormat = elem.attrib.get(cls.NAME_FORMAT_ATTRIB_NAME)   
583        if nameFormat is not None:
584            attribute.nameFormat = nameFormat
585       
586        # Factory to handle the different Attribute Value types
587        factory = AttributeValueElementTreeFactory(
588                                        **attributeValueElementTreeFactoryKw)
589
590        for childElem in elem:
591            localName = QName.getLocalPart(childElem.tag)
592            if localName != AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME:
593                raise XMLTypeParseError('Expecting "%s" element; found "%s"'%
594                                    (AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME,
595                                     localName))
596                           
597            attributeValueElementTreeClass = factory(childElem)
598            attributeValue = attributeValueElementTreeClass.fromXML(childElem)
599            attribute.attributeValues.append(attributeValue)
600       
601        return attribute
602       
603   
604class AttributeValueElementTreeBase(AttributeValue):
605    """Base class ElementTree XML representation of SAML Attribute Value""" 
606   
607    @classmethod
608    def toXML(cls, attributeValue):
609        """Make a tree of a XML elements based on the Attribute value
610       
611        @type assertion: saml.saml2.core.Assertion
612        @param assertion: Assertion to be represented as an ElementTree Element
613        @rtype: ElementTree.Element
614        @return: ElementTree Element
615        """
616        if not isinstance(attributeValue, AttributeValue):
617            raise TypeError("Expecting %r type got: %r" % (AttributeValue, 
618                                                           attributeValue))
619           
620        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))   
621        elem = ElementTree.Element(tag)
622        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
623                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
624
625        return elem
626
627
628class XSStringAttributeValueElementTree(AttributeValueElementTreeBase,
629                                        XSStringAttributeValue):
630    """ElementTree XML representation of SAML String type Attribute Value""" 
631   
632    @classmethod
633    def toXML(cls, attributeValue):
634        """Create an XML representation of the input SAML Attribute Value
635       
636        @type assertion: saml.saml2.core.XSStringAttributeValue
637        @param assertion: xs:string to be represented as an ElementTree Element
638        @rtype: ElementTree.Element
639        @return: ElementTree Element
640        """
641        elem = AttributeValueElementTreeBase.toXML(attributeValue)
642       
643        if not isinstance(attributeValue, XSStringAttributeValue):
644            raise TypeError("Expecting %r type got: %r" % 
645                            (XSStringAttributeValue, attributeValue)) 
646       
647        # Have to explicitly add namespace declaration here rather use
648        # ElementTree._namespace_map because the prefixes are used for
649        # attributes not element names       
650        elem.set("%s:%s" % (SAMLConstants.XMLNS_PREFIX, 
651                            SAMLConstants.XSD_PREFIX),
652                 SAMLConstants.XSD_NS)
653                                   
654        elem.set("%s:%s" % (SAMLConstants.XMLNS_PREFIX, 
655                            SAMLConstants.XSI_PREFIX),
656                 SAMLConstants.XSI_NS)
657       
658        elem.set("%s:%s" % (SAMLConstants.XSI_PREFIX, 'type'), 
659                 "%s:%s" % (SAMLConstants.XSD_PREFIX, 
660                            cls.TYPE_LOCAL_NAME))
661
662        elem.text = attributeValue.value
663
664        return elem
665
666    @classmethod
667    def fromXML(cls, elem):
668        """Parse ElementTree xs:string element into a SAML
669        XSStringAttributeValue object
670       
671        @type elem: ElementTree.Element
672        @param elem: Attribute value as ElementTree XML element
673        @rtype: saml.saml2.core.AttributeValue
674        @return: SAML Attribute value
675        """
676        if not ElementTree.iselement(elem):
677            raise TypeError("Expecting %r input type for parsing; got %r" %
678                            (ElementTree.Element, elem))
679
680        localName = QName.getLocalPart(elem.tag)
681        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
682            raise XMLTypeParseError("No \"%s\" element found" %
683                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
684       
685        # Parse the attribute type checking that it is set to the expected
686        # string type
687        typeQName = QName(SAMLConstants.XSI_NS, tag='type')
688       
689        typeValue = elem.attrib.get(str(typeQName), '')
690        typeValueLocalName = typeValue.split(':')[-1]
691        if typeValueLocalName != cls.TYPE_LOCAL_NAME:
692            raise XMLTypeParseError('Expecting "%s" type; got "%s"' %
693                                      (cls.TYPE_LOCAL_NAME,
694                                       typeValueLocalName))
695       
696        # Update namespace map as an XSI type has been referenced.  This will
697        # ensure the correct prefix is applied if it is re-serialised.
698        ElementTree._namespace_map[SAMLConstants.XSI_NS
699                                   ] = SAMLConstants.XSI_PREFIX
700                                     
701        attributeValue = XSStringAttributeValue()
702        if elem.text is not None:
703            attributeValue.value = elem.text.strip()
704
705        return attributeValue
706
707
708class XSGroupRoleAttributeValueElementTree(AttributeValueElementTreeBase,
709                                           XSGroupRoleAttributeValue):
710    """ElementTree XML representation of Earth System Grid custom Group/Role
711    Attribute Value""" 
712
713    @classmethod
714    def toXML(cls, attributeValue):
715        """Create an XML representation of the input SAML ESG Group/Role type
716        Attribute Value
717       
718        @type assertion: saml.saml2.core.XSGroupRoleAttributeValue
719        @param assertion: XSGroupRoleAttributeValue to be represented as an
720        ElementTree Element
721        @rtype: ElementTree.Element
722        @return: ElementTree Element
723        """
724        elem = AttributeValueElementTreeBase.toXML(attributeValue)
725       
726        if not isinstance(attributeValue, XSGroupRoleAttributeValue):
727            raise TypeError("Expecting %r type; got: %r" % 
728                            (XSGroupRoleAttributeValue, type(attributeValue)))
729           
730        ElementTree._namespace_map[attributeValue.namespaceURI
731                                   ] = attributeValue.namespacePrefix
732       
733        elem.set(cls.GROUP_ATTRIB_NAME, attributeValue.group)
734        elem.set(cls.ROLE_ATTRIB_NAME, attributeValue.role)
735
736        return elem
737
738    @classmethod
739    def fromXML(cls, elem):
740        """Parse ElementTree ESG Group/Role attribute element into a SAML
741        XSGroupRoleAttributeValue object
742       
743        @type elem: ElementTree.Element
744        @param elem: Attribute value as ElementTree XML element
745        @rtype: saml.saml2.core.XSGroupRoleAttributeValue
746        @return: SAML ESG Group/Role Attribute value
747        """
748        if not ElementTree.iselement(elem):
749            raise TypeError("Expecting %r input type for parsing; got %r" %
750                            (ElementTree.Element, elem))
751
752        localName = QName.getLocalPart(elem.tag)
753        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
754            raise XMLTypeParseError("No \"%s\" element found" %
755                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
756
757       
758        # Update namespace map as an XSI type has been referenced.  This will
759        # ensure the correct prefix is applied if it is re-serialised.
760        ElementTree._namespace_map[SAMLConstants.XSI_NS
761                                   ] = SAMLConstants.XSI_PREFIX
762                                     
763        attributeValue = XSGroupRoleAttributeValue()
764        if elem.text is not None:
765            attributeValue.value = elem.text.strip()
766
767        return attributeValue
768   
769    @classmethod
770    def factoryMatchFunc(cls, elem):
771        """Match function used by AttributeValueElementTreeFactory to
772        determine whether the given attribute is XSGroupRole type
773        """
774        if cls.GROUP_ATTRIB_NAME in elem.attrib and \
775           cls.ROLE_ATTRIB_NAME in elem.attrib:
776            return cls
777
778        return None
779
780
781class AttributeValueElementTreeFactory(object):
782    """Class factory for AttributeValue ElementTree classes.  These classes are
783    used to represent SAML Attribute value types
784   
785    @type toXMLTypeMap: dict
786    @cvar toXMLTypeMap: mapping between SAML AttributeValue class and its
787    ElementTree handler class
788    @type toSAMLTypeMap: dict
789    @cvar toSAMLTypeMap: mapping between SAML AttributeValue string identifier and
790    its ElementTree handler class
791    """
792    toXMLTypeMap = {
793        XSStringAttributeValue: XSStringAttributeValueElementTree
794    }
795
796    def xsstringMatch(elem):
797        """Match function for xs:string type attribute.
798        @type elem: ElementTree.Element
799        @param elem: Attribute Value element to be checked
800        @rtype: XSStringAttributeValueElementTree/None
801        @return: Parsing class if this element is an xs:string Attribute Value,
802        None otherwise.
803        """
804        # Iterate through the attributes searching for a type attribute set to
805        # xs:string
806        for attribName, attribVal in elem.attrib.items():
807            qname = QName(attribName)
808            if qname.localPart == "type":
809                typeLocalName = attribVal.split(':')[-1]
810               
811                if typeLocalName == XSStringAttributeValue.TYPE_LOCAL_NAME:
812                    return XSStringAttributeValueElementTree
813                else:
814                    return None
815               
816        # No type attribute was found for this Attribute element
817        return None
818       
819    toSAMLTypeMap = [xsstringMatch]
820    xsstringMatch = staticmethod(toSAMLTypeMap[0])
821   
822    def __init__(self, customToXMLTypeMap={}, customToSAMLTypeMap=[]): 
823        """Set-up a SAML class to ElementTree mapping
824        @type customToXMLTypeMap: dict
825        @param customToXMLTypeMap: mapping for custom SAML AttributeValue
826        classes to their respective ElementTree based representations.  This
827        appends to self.__toXMLTypeMap
828        @type customToSAMLTypeMap: dict
829        @param customToSAMLTypeMap: string ID based mapping for custom SAML
830        AttributeValue classes to their respective ElementTree based
831        representations.  As with customToXMLTypeMap, this appends to
832        to the respective self.__toSAMLTypeMap
833        """
834        self.__toXMLTypeMap = AttributeValueElementTreeFactory.toXMLTypeMap
835        if not isinstance(customToXMLTypeMap, dict):
836            raise TypeError('Expecting dict type for "customToXMLTypeMap"')
837
838        for samlClass, etreeClass in customToXMLTypeMap.items(): 
839            if not issubclass(samlClass, AttributeValue):
840                raise TypeError("Input custom class must be derived from %r, "
841                                "got %r instead" % (Attribute, samlClass))
842               
843            self.__toXMLTypeMap[samlClass] = etreeClass
844
845        if not isinstance(customToSAMLTypeMap, (list, tuple)):
846            raise TypeError('Expecting list or tuple type for '
847                            '"customToSAMLTypeMap"')
848       
849        self.__toSAMLTypeMap = AttributeValueElementTreeFactory.toSAMLTypeMap[:]
850        for func in customToSAMLTypeMap:
851            if not callable(func):
852                raise TypeError('"customToSAMLTypeMap" items must be callable')
853           
854        self.__toSAMLTypeMap += customToSAMLTypeMap
855
856    def __call__(self, input):
857        """Create an ElementTree object based on the Attribute class type
858        passed in
859       
860        @type input: saml.saml2.core.AttributeValue or basestring
861        @param input: pass an AttributeValue derived type or a string.  If
862        an AttributeValue type, then self.__toXMLTypeMap is checked for a matching
863        AttributeValue class entry, if a string is passed, self.__toSAMLTypeMap is
864        checked for a matching string ID.  In both cases, if a match is
865        found an ElementTree class is returned which can render or parse
866        the relevant AttributeValue class
867        """
868        if isinstance(input, AttributeValue):
869            XMLTypeClass = self.__toXMLTypeMap.get(input.__class__)
870            if XMLTypeClass is None:
871                raise TypeError("no matching XMLType class representation "
872                                "for SAML class %r" % input.__class__)
873               
874        elif ElementTree.iselement(input):
875            XMLTypeClasses = []
876            for matchFunc in self.__toSAMLTypeMap:
877                cls = matchFunc(input)
878                if cls is None:
879                    continue
880                elif issubclass(cls, AttributeValue):
881                    XMLTypeClasses.append(cls)
882                else:
883                    raise TypeError("Expecting AttributeValue derived type "
884                                    "for XML class; got %r" % cls)
885           
886            nXMLTypeClasses = len(XMLTypeClasses)
887            if nXMLTypeClasses == 0:
888                raise TypeError("no matching XMLType class representation "
889                                "for SAML AttributeValue type %r" % input)
890            elif nXMLTypeClasses > 1:
891                raise TypeError("Multiple XMLType classes %r matched for "
892                                "for SAML AttributeValue type %r" % 
893                                (XMLTypeClasses, input)) 
894                   
895            XMLTypeClass = XMLTypeClasses[0]           
896        else:
897            raise TypeError("Expecting %r class got %r" % (AttributeValue, 
898                                                           type(input)))
899        return XMLTypeClass
900   
901
902class IssuerElementTree(Issuer):
903    """Represent a SAML Issuer element in XML using ElementTree"""
904   
905    @classmethod
906    def toXML(cls, issuer):
907        """Create an XML representation of the input SAML issuer object"""
908        if not isinstance(issuer, Issuer):
909            raise TypeError("Expecting %r class got %r" % (Issuer, 
910                                                           type(issuer)))
911        attrib = {
912            cls.FORMAT_ATTRIB_NAME: issuer.format
913        }
914        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
915        elem = ElementTree.Element(tag, **attrib)
916        ElementTree._namespace_map[issuer.qname.namespaceURI
917                                   ] = issuer.qname.prefix
918                                   
919        elem.text = issuer.value
920
921        return elem
922
923    @classmethod
924    def fromXML(cls, elem):
925        """Parse ElementTree element into a SAML Issuer instance
926       
927        @type elem: ElementTree.Element
928        @param elem: ElementTree element containing the assertion
929        @rtype: saml.saml2.core.Issuer
930        @return: Assertion object"""
931        if not ElementTree.iselement(elem):
932            raise TypeError("Expecting %r input type for parsing; got %r" %
933                            (ElementTree.Element, elem))
934
935        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
936            raise XMLTypeParseError('No "%s" element found' %
937                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
938           
939        issuerFormat = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
940        if issuerFormat is None:
941            raise XMLTypeParseError('No "%s" attribute found in "%s" '
942                                      'element' %
943                                      (issuerFormat,
944                                       cls.DEFAULT_ELEMENT_LOCAL_NAME))
945        issuer = Issuer()
946        issuer.format = issuerFormat
947        issuer.value = elem.text.strip() 
948       
949        return issuer
950
951       
952class NameIdElementTree(NameID):
953    """Represent a SAML Name Identifier in XML using ElementTree"""
954   
955    @classmethod
956    def toXML(cls, nameID):
957        """Create an XML representation of the input SAML Name Identifier
958        object
959        @type nameID: saml.saml2.core.Subject
960        @param nameID: SAML subject
961        @rtype: ElementTree.Element
962        @return: Name ID as ElementTree XML element"""
963       
964        if not isinstance(nameID, NameID):
965            raise TypeError("Expecting %r class got %r" % (NameID, 
966                                                           type(nameID)))
967        attrib = {
968            cls.FORMAT_ATTRIB_NAME: nameID.format
969        }
970        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
971        elem = ElementTree.Element(tag, **attrib)
972       
973        ElementTree._namespace_map[nameID.qname.namespaceURI
974                                   ] = nameID.qname.prefix
975       
976        elem.text = nameID.value
977
978        return elem
979
980    @classmethod
981    def fromXML(cls, elem):
982        """Parse ElementTree element into a SAML NameID object
983       
984        @type elem: ElementTree.Element
985        @param elem: Name ID as ElementTree XML element
986        @rtype: saml.saml2.core.NameID
987        @return: SAML Name ID
988        """
989        if not ElementTree.iselement(elem):
990            raise TypeError("Expecting %r input type for parsing; got %r" %
991                            (ElementTree.Element, elem))
992
993        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
994            raise XMLTypeParseError("No \"%s\" element found" %
995                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
996           
997        format = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
998        if format is None:
999            raise XMLTypeParseError('No "%s" attribute found in "%s" '
1000                                      'element' %
1001                                      (format,
1002                                       cls.DEFAULT_ELEMENT_LOCAL_NAME))
1003        nameID = NameID()
1004        nameID.format = format
1005        nameID.value = elem.text.strip() 
1006       
1007        return nameID
1008
1009
1010class SubjectElementTree(Subject):
1011    """Represent a SAML Subject in XML using ElementTree"""
1012   
1013    @classmethod
1014    def toXML(cls, subject):
1015        """Create an XML representation of the input SAML subject object
1016        @type subject: saml.saml2.core.Subject
1017        @param subject: SAML subject
1018        @rtype: ElementTree.Element
1019        @return: subject as ElementTree XML element
1020        """
1021        if not isinstance(subject, Subject):
1022            raise TypeError("Expecting %r class got %r" % (Subject, 
1023                                                           type(subject)))
1024           
1025        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
1026        elem = ElementTree.Element(tag)
1027       
1028        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1029                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1030
1031           
1032        nameIdElem = NameIdElementTree.toXML(subject.nameID)
1033        elem.append(nameIdElem)
1034       
1035        return elem
1036
1037    @classmethod
1038    def fromXML(cls, elem):
1039        """Parse ElementTree element into a SAML Subject object
1040       
1041        @type elem: ElementTree.Element
1042        @param elem: subject as ElementTree XML element
1043        @rtype: saml.saml2.core.Subject
1044        @return: SAML subject
1045        """
1046        if not ElementTree.iselement(elem):
1047            raise TypeError("Expecting %r input type for parsing; got %r" %
1048                            (ElementTree.Element, elem))
1049
1050        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1051            raise XMLTypeParseError("No \"%s\" element found" %
1052                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
1053           
1054        if len(elem) != 1:
1055            raise XMLTypeParseError("Expecting single Name ID child element "
1056                                      "for SAML Subject element")
1057           
1058        subject = Subject()
1059        subject.nameID = NameIdElementTree.fromXML(elem[0])
1060       
1061        return subject
1062
1063       
1064class StatusCodeElementTree(StatusCode):
1065    """Represent a SAML Name Identifier in XML using ElementTree"""
1066   
1067    @classmethod
1068    def toXML(cls, statusCode):
1069        """Create an XML representation of the input SAML Name Status Code
1070       
1071        @type statusCode: saml.saml2.core.StatusCode
1072        @param statusCode: SAML Status Code
1073        @rtype: ElementTree.Element
1074        @return: Status Code as ElementTree XML element"""
1075       
1076        if not isinstance(statusCode, StatusCode):
1077            raise TypeError("Expecting %r class got %r" % (StatusCode, 
1078                                                           type(statusCode)))
1079           
1080        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1081        elem = ElementTree.Element(tag)
1082       
1083        ElementTree._namespace_map[statusCode.qname.namespaceURI
1084                                   ] = statusCode.qname.prefix
1085       
1086        elem.text = statusCode.value
1087
1088        return elem
1089
1090    @classmethod
1091    def fromXML(cls, elem):
1092        """Parse ElementTree element into a SAML StatusCode object
1093       
1094        @type elem: ElementTree.Element
1095        @param elem: Status Code as ElementTree XML element
1096        @rtype: saml.saml2.core.StatusCode
1097        @return: SAML Status Code
1098        """
1099        if not ElementTree.iselement(elem):
1100            raise TypeError("Expecting %r input type for parsing; got %r" %
1101                            (ElementTree.Element, elem))
1102
1103        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1104            raise XMLTypeParseError('No "%s" element found' %
1105                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
1106           
1107        statusCode = StatusCode()
1108        if elem.text is not None:
1109            statusCode.value = elem.text.strip() 
1110       
1111        return statusCode
1112
1113
1114class StatusElementTree(Status):
1115    """Represent a SAML Status in XML using ElementTree"""
1116   
1117    @classmethod
1118    def toXML(cls, status):
1119        """Create an XML representation of the input SAML subject object
1120        @type subject: saml.saml2.core.Status
1121        @param subject: SAML subject
1122        @rtype: ElementTree.Element
1123        @return: subject as ElementTree XML element
1124        """
1125        if not isinstance(status, Status):
1126            raise TypeError("Expecting %r class got %r" % (status, 
1127                                                           type(Status)))
1128           
1129        tag = str(QName.fromGeneric(Status.DEFAULT_ELEMENT_NAME)) 
1130        elem = ElementTree.Element(tag)
1131       
1132        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1133                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1134       
1135        statusCodeElem = StatusCodeElementTree.toXML(status.statusCode)
1136        elem.append(statusCodeElem)
1137       
1138        return elem
1139
1140    @classmethod
1141    def fromXML(cls, elem):
1142        """Parse ElementTree element into a SAML Status object
1143       
1144        @type elem: ElementTree.Element
1145        @param elem: subject as ElementTree XML element
1146        @rtype: saml.saml2.core.Status
1147        @return: SAML subject
1148        """
1149        if not ElementTree.iselement(elem):
1150            raise TypeError("Expecting %r input type for parsing; got %r" %
1151                            (ElementTree.Element, elem))
1152
1153        if QName.getLocalPart(elem.tag) != Status.DEFAULT_ELEMENT_LOCAL_NAME:
1154            raise XMLTypeParseError('No "%s" element found' %
1155                                      Status.DEFAULT_ELEMENT_LOCAL_NAME)
1156           
1157        if len(elem) != 1:
1158            raise XMLTypeParseError("Expecting single StatusCode child "
1159                                      "element for SAML Status element")
1160           
1161        status = Status()
1162        status.statusCode = StatusCodeElementTree.fromXML(elem[0])
1163       
1164        return status
1165   
1166   
1167class AttributeQueryElementTree(AttributeQuery):
1168    """Represent a SAML Attribute Query in XML using ElementTree"""
1169       
1170    @classmethod
1171    def toXML(cls, attributeQuery, **attributeValueElementTreeFactoryKw):
1172        """Create an XML representation of the input SAML Attribute Query
1173        object
1174
1175        @type attributeQuery: saml.saml2.core.AttributeQuery
1176        @param attributeQuery: SAML Attribute Query
1177        @type attributeValueElementTreeFactoryKw: dict
1178        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1179        factory
1180        @rtype: ElementTree.Element
1181        @return: Attribute Query as ElementTree XML element
1182        """
1183        if not isinstance(attributeQuery, AttributeQuery):
1184            raise TypeError("Expecting %r class got %r" % (AttributeQuery, 
1185                                                        type(attributeQuery)))
1186           
1187       
1188        issueInstant = SAMLDateTime.toString(attributeQuery.issueInstant)
1189        attrib = {
1190            cls.ID_ATTRIB_NAME: attributeQuery.id,
1191            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1192           
1193            # Nb. Version is a SAMLVersion instance and requires explicit cast
1194            cls.VERSION_ATTRIB_NAME: str(attributeQuery.version)
1195        }
1196       
1197        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1198        elem = ElementTree.Element(tag, **attrib)
1199       
1200        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1201                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1202       
1203        issuerElem = IssuerElementTree.toXML(attributeQuery.issuer)
1204        elem.append(issuerElem)
1205
1206        subjectElem = SubjectElementTree.toXML(attributeQuery.subject)
1207        elem.append(subjectElem)
1208
1209        for attribute in attributeQuery.attributes:
1210            # Factory enables support for multiple attribute types
1211            attributeElem = AttributeElementTree.toXML(attribute,
1212                                        **attributeValueElementTreeFactoryKw)
1213            elem.append(attributeElem)
1214       
1215        return elem
1216
1217    @classmethod
1218    def fromXML(cls, elem):
1219        """Parse ElementTree element into a SAML AttributeQuery object
1220       
1221        @type elem: ElementTree.Element
1222        @param elem: XML element containing the AttributeQuery
1223        @rtype: saml.saml2.core.AttributeQuery
1224        @return: AttributeQuery object
1225        """
1226        if not ElementTree.iselement(elem):
1227            raise TypeError("Expecting %r input type for parsing; got %r" %
1228                            (ElementTree.Element, elem))
1229
1230        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1231            raise XMLTypeParseError("No \"%s\" element found" %
1232                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1233       
1234        # Unpack attributes from top-level element
1235        attributeValues = []
1236        for attributeName in (cls.VERSION_ATTRIB_NAME,
1237                              cls.ISSUE_INSTANT_ATTRIB_NAME,
1238                              cls.ID_ATTRIB_NAME):
1239            attributeValue = elem.attrib.get(attributeName)
1240            if attributeValue is None:
1241                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1242                                 'element' %
1243                                 (attributeName,
1244                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
1245               
1246            attributeValues.append(attributeValue)
1247       
1248        attributeQuery = AttributeQuery()
1249        attributeQuery.version = SAMLVersion(attributeValues[0])
1250        if attributeQuery.version != SAMLVersion.VERSION_20:
1251            raise NotImplementedError("Parsing for %r is implemented for "
1252                                      "SAML version %s only; version %s is " 
1253                                      "not supported" % 
1254                                      (cls,
1255                                       SAMLVersion(SAMLVersion.VERSION_20),
1256                                       SAMLVersion(attributeQuery.version)))
1257           
1258        attributeQuery.issueInstant = SAMLDateTime.fromString(
1259                                                            attributeValues[1])
1260        attributeQuery.id = attributeValues[2]
1261       
1262        for childElem in elem:
1263            localName = QName.getLocalPart(childElem.tag)
1264            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1265                # Parse Issuer
1266                attributeQuery.issuer = IssuerElementTree.fromXML(childElem)
1267               
1268            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1269                # Parse Subject
1270                attributeQuery.subject = SubjectElementTree.fromXML(childElem)
1271           
1272            elif localName == Attribute.DEFAULT_ELEMENT_LOCAL_NAME:
1273                attribute = AttributeElementTree.fromXML(childElem)
1274                attributeQuery.attributes.append(attribute)
1275            else:
1276                raise XMLTypeParseError("Unrecognised AttributeQuery child "
1277                                          "element \"%s\"" % localName)
1278       
1279        return attributeQuery
1280       
1281   
1282class ResponseElementTree(Response):
1283    """Represent a SAML Response in XML using ElementTree"""
1284       
1285    @classmethod
1286    def toXML(cls, response, **attributeValueElementTreeFactoryKw):
1287        """Create an XML representation of the input SAML Response
1288        object
1289
1290        @type response: saml.saml2.core.Response
1291        @param response: SAML Response
1292        @type attributeValueElementTreeFactoryKw: dict
1293        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1294        factory
1295        @rtype: ElementTree.Element
1296        @return: Response as ElementTree XML element
1297        """
1298        if not isinstance(response, Response):
1299            raise TypeError("Expecting %r class, got %r" % (Response, 
1300                                                            type(response)))
1301           
1302        issueInstant = SAMLDateTime.toString(response.issueInstant)
1303        attrib = {
1304            cls.ID_ATTRIB_NAME: response.id,
1305            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1306            cls.IN_RESPONSE_TO_ATTRIB_NAME: response.inResponseTo,
1307           
1308            # Nb. Version is a SAMLVersion instance and requires explicit cast
1309            cls.VERSION_ATTRIB_NAME: str(response.version)
1310        }
1311       
1312        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))       
1313        elem = ElementTree.Element(tag, **attrib)
1314       
1315        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1316                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1317           
1318        issuerElem = IssuerElementTree.toXML(response.issuer)
1319        elem.append(issuerElem)
1320
1321        statusElem = StatusElementTree.toXML(response.status)       
1322        elem.append(statusElem)
1323
1324        for assertion in response.assertions:
1325            # Factory enables support for multiple attribute types
1326            assertionElem = AssertionElementTree.toXML(assertion,
1327                                        **attributeValueElementTreeFactoryKw)
1328            elem.append(assertionElem)
1329       
1330        return elem
1331
1332    @classmethod
1333    def fromXML(cls, elem):
1334        """Parse ElementTree element into a SAML Response object
1335       
1336        @type elem: ElementTree.Element
1337        @param elem: XML element containing the Response
1338        @rtype: saml.saml2.core.Response
1339        @return: Response object
1340        """
1341        if not ElementTree.iselement(elem):
1342            raise TypeError("Expecting %r input type for parsing; got %r" %
1343                            (ElementTree.Element, elem))
1344
1345        if QName.getLocalPart(elem.tag) != Response.DEFAULT_ELEMENT_LOCAL_NAME:
1346            raise XMLTypeParseError("No \"%s\" element found" %
1347                                      Response.DEFAULT_ELEMENT_LOCAL_NAME)
1348       
1349        # Unpack attributes from top-level element
1350        attributeValues = []
1351        for attributeName in (Response.VERSION_ATTRIB_NAME,
1352                              Response.ISSUE_INSTANT_ATTRIB_NAME,
1353                              Response.ID_ATTRIB_NAME,
1354                              Response.IN_RESPONSE_TO_ATTRIB_NAME):
1355            attributeValue = elem.attrib.get(attributeName)
1356            if attributeValue is None:
1357                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1358                                          'element' %
1359                                         (attributeName,
1360                                          Response.DEFAULT_ELEMENT_LOCAL_NAME))
1361               
1362            attributeValues.append(attributeValue)
1363       
1364        response = Response()
1365        response.version = SAMLVersion(attributeValues[0])
1366        if response.version != SAMLVersion.VERSION_20:
1367            raise NotImplementedError("Parsing for %r is implemented for "
1368                                      "SAML version %s only; version %s is " 
1369                                      "not supported" % 
1370                                      (cls,
1371                                       SAMLVersion(SAMLVersion.VERSION_20),
1372                                       SAMLVersion(response.version)))
1373           
1374        response.issueInstant = SAMLDateTime.fromString(attributeValues[1])
1375        response.id = attributeValues[2]
1376        response.inResponseTo = attributeValues[3]
1377       
1378        for childElem in elem:
1379            localName = QName.getLocalPart(childElem.tag)
1380            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1381                # Parse Issuer
1382                response.issuer = IssuerElementTree.fromXML(childElem)
1383           
1384            elif localName == Status.DEFAULT_ELEMENT_LOCAL_NAME:
1385                # Get status of response
1386                response.status = StatusElementTree.fromXML(childElem)
1387               
1388            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1389                # Parse Subject
1390                response.subject = SubjectElementTree.fromXML(childElem)
1391           
1392            elif localName == Assertion.DEFAULT_ELEMENT_LOCAL_NAME:
1393                response.assertions.append(
1394                                    AssertionElementTree.fromXML(childElem))
1395            else:
1396                raise XMLTypeParseError('Unrecognised Response child '
1397                                          'element "%s"' % localName)
1398       
1399        return response
1400
Note: See TracBrowser for help on using the repository browser.