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

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