source: TI12-security/trunk/python/ndg_security_saml/saml/xml/etree.py @ 6069

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

Re-release as rc1

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