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

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

saml.xml.etree: important fixes to ElementTree based Status element serialisation and de-serialisation
ndg.security.server.attributeauthority: added clockSkew parameter to provide some leeway in SAML attribute query clock checks. Also added StatusMessage? element for additional error info in responses.
ndg.security.common.soap.client: added check of HTTP Content-type in SOAP responses.

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, StatusMessage, StatusDetail, Advice, \
44    XSStringAttributeValue, 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, UnknownAttrProfile
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                                    (cls.FORMAT_ATTRIB_NAME,
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 Status Code 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        attrib = {
1163            cls.VALUE_ATTRIB_NAME: statusCode.value
1164        }
1165        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1166        elem = ElementTree.Element(tag, **attrib)
1167       
1168        ElementTree._namespace_map[statusCode.qname.namespaceURI
1169                                   ] = statusCode.qname.prefix
1170
1171        return elem
1172
1173    @classmethod
1174    def fromXML(cls, elem):
1175        """Parse ElementTree element into a SAML StatusCode object
1176       
1177        @type elem: ElementTree.Element
1178        @param elem: Status Code as ElementTree XML element
1179        @rtype: saml.saml2.core.StatusCode
1180        @return: SAML Status Code
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) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1187            raise XMLTypeParseError('No "%s" element found' %
1188                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1189           
1190        statusCode = StatusCode()
1191           
1192        value = elem.attrib.get(cls.VALUE_ATTRIB_NAME)
1193        if value is None:
1194            raise XMLTypeParseError('No "%s" attribute found in "%s" element' %
1195                                    (cls.VALUE_ATTRIB_NAME,
1196                                     cls.DEFAULT_ELEMENT_LOCAL_NAME))
1197        statusCode.value = value
1198       
1199        return statusCode
1200
1201       
1202class StatusMessageElementTree(StatusMessage):
1203    """Represent a SAML Status Message in XML using ElementTree"""
1204   
1205    @classmethod
1206    def toXML(cls, statusMessage):
1207        """Create an XML representation of the input SAML Name Status Message
1208       
1209        @type statusMessage: saml.saml2.core.StatusMessage
1210        @param statusMessage: SAML Status Message
1211        @rtype: ElementTree.Element
1212        @return: Status Code as ElementTree XML element"""
1213       
1214        if not isinstance(statusMessage, StatusMessage):
1215            raise TypeError("Expecting %r class got %r" % (StatusMessage, 
1216                                                           type(statusMessage)))
1217           
1218        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1219        elem = ElementTree.Element(tag)
1220       
1221        ElementTree._namespace_map[statusMessage.qname.namespaceURI
1222                                   ] = statusMessage.qname.prefix
1223       
1224        elem.text = statusMessage.value
1225
1226        return elem
1227
1228    @classmethod
1229    def fromXML(cls, elem):
1230        """Parse ElementTree element into a SAML StatusMessage object
1231       
1232        @type elem: ElementTree.Element
1233        @param elem: Status Code as ElementTree XML element
1234        @rtype: saml.saml2.core.StatusMessage
1235        @return: SAML Status Message
1236        """
1237        if not ElementTree.iselement(elem):
1238            raise TypeError("Expecting %r input type for parsing; got %r" %
1239                            (ElementTree.Element, elem))
1240
1241        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1242            raise XMLTypeParseError('No "%s" element found' %
1243                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1244           
1245        statusMessage = StatusMessage()
1246        if elem.text is not None:
1247            statusMessage.value = elem.text.strip() 
1248       
1249        return statusMessage
1250
1251
1252class StatusElementTree(Status):
1253    """Represent a SAML Status in XML using ElementTree"""
1254   
1255    @classmethod
1256    def toXML(cls, status):
1257        """Create an XML representation of the input SAML subject object
1258        @type subject: saml.saml2.core.Status
1259        @param subject: SAML subject
1260        @rtype: ElementTree.Element
1261        @return: subject as ElementTree XML element
1262        """
1263        if not isinstance(status, Status):
1264            raise TypeError("Expecting %r class got %r" % (status, 
1265                                                           type(Status)))
1266           
1267        tag = str(QName.fromGeneric(Status.DEFAULT_ELEMENT_NAME)) 
1268        elem = ElementTree.Element(tag)
1269       
1270        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1271                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1272       
1273        statusCodeElem = StatusCodeElementTree.toXML(status.statusCode)
1274        elem.append(statusCodeElem)
1275       
1276        # Status message is optional
1277        if status.statusMessage is not None and \
1278           status.statusMessage.value is not None:
1279            statusMessageElem = StatusMessageElementTree.toXML(
1280                                                        status.statusMessage)
1281            elem.append(statusMessageElem)
1282       
1283        if status.statusDetail is not None:
1284            raise NotImplementedError("StatusDetail XML serialisation is not "
1285                                      "implemented")
1286           
1287        return elem
1288
1289    @classmethod
1290    def fromXML(cls, elem):
1291        """Parse ElementTree element into a SAML Status object
1292       
1293        @type elem: ElementTree.Element
1294        @param elem: subject as ElementTree XML element
1295        @rtype: saml.saml2.core.Status
1296        @return: SAML subject
1297        """
1298        if not ElementTree.iselement(elem):
1299            raise TypeError("Expecting %r input type for parsing; got %r" %
1300                            (ElementTree.Element, elem))
1301
1302        if QName.getLocalPart(elem.tag) != Status.DEFAULT_ELEMENT_LOCAL_NAME:
1303            raise XMLTypeParseError('No "%s" element found' %
1304                                      Status.DEFAULT_ELEMENT_LOCAL_NAME)
1305           
1306        if len(elem) < 1:
1307            raise XMLTypeParseError("Expecting a StatusCode child element for "
1308                                    "SAML Status element")
1309           
1310        status = Status()
1311        for childElem in elem:
1312            localName = QName.getLocalPart(childElem.tag)
1313            if localName == StatusCode.DEFAULT_ELEMENT_LOCAL_NAME:
1314                status.statusCode = StatusCodeElementTree.fromXML(childElem)
1315               
1316            elif localName == StatusMessage.DEFAULT_ELEMENT_LOCAL_NAME:
1317                status.statusMessage = StatusMessageElementTree.fromXML(
1318                                                                childElem)
1319            elif localName == StatusDetail.DEFAULT_ELEMENT_LOCAL_NAME:
1320                raise NotImplementedError("XML parse of %s element is not "
1321                                    "implemented" %
1322                                    StatusDetail.DEFAULT_ELEMENT_LOCAL_NAME)
1323            else:
1324                raise XMLTypeParseError("Status child element name %r not "
1325                                        "recognised" % localName)
1326       
1327        return status
1328   
1329   
1330class AttributeQueryElementTree(AttributeQuery):
1331    """Represent a SAML Attribute Query in XML using ElementTree"""
1332       
1333    @classmethod
1334    def toXML(cls, attributeQuery, **attributeValueElementTreeFactoryKw):
1335        """Create an XML representation of the input SAML Attribute Query
1336        object
1337
1338        @type attributeQuery: saml.saml2.core.AttributeQuery
1339        @param attributeQuery: SAML Attribute Query
1340        @type attributeValueElementTreeFactoryKw: dict
1341        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1342        factory
1343        @rtype: ElementTree.Element
1344        @return: Attribute Query as ElementTree XML element
1345        """
1346        if not isinstance(attributeQuery, AttributeQuery):
1347            raise TypeError("Expecting %r class got %r" % (AttributeQuery, 
1348                                                        type(attributeQuery)))
1349           
1350       
1351        issueInstant = SAMLDateTime.toString(attributeQuery.issueInstant)
1352        attrib = {
1353            cls.ID_ATTRIB_NAME: attributeQuery.id,
1354            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1355           
1356            # Nb. Version is a SAMLVersion instance and requires explicit cast
1357            cls.VERSION_ATTRIB_NAME: str(attributeQuery.version)
1358        }
1359       
1360        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1361        elem = ElementTree.Element(tag, **attrib)
1362       
1363        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1364                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1365       
1366        issuerElem = IssuerElementTree.toXML(attributeQuery.issuer)
1367        elem.append(issuerElem)
1368
1369        subjectElem = SubjectElementTree.toXML(attributeQuery.subject)
1370        elem.append(subjectElem)
1371
1372        for attribute in attributeQuery.attributes:
1373            # Factory enables support for multiple attribute types
1374            attributeElem = AttributeElementTree.toXML(attribute,
1375                                        **attributeValueElementTreeFactoryKw)
1376            elem.append(attributeElem)
1377       
1378        return elem
1379
1380    @classmethod
1381    def fromXML(cls, elem):
1382        """Parse ElementTree element into a SAML AttributeQuery object
1383       
1384        @type elem: ElementTree.Element
1385        @param elem: XML element containing the AttributeQuery
1386        @rtype: saml.saml2.core.AttributeQuery
1387        @return: AttributeQuery object
1388        """
1389        if not ElementTree.iselement(elem):
1390            raise TypeError("Expecting %r input type for parsing; got %r" %
1391                            (ElementTree.Element, elem))
1392
1393        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1394            raise XMLTypeParseError("No \"%s\" element found" %
1395                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1396       
1397        # Unpack attributes from top-level element
1398        attributeValues = []
1399        for attributeName in (cls.VERSION_ATTRIB_NAME,
1400                              cls.ISSUE_INSTANT_ATTRIB_NAME,
1401                              cls.ID_ATTRIB_NAME):
1402            attributeValue = elem.attrib.get(attributeName)
1403            if attributeValue is None:
1404                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1405                                 'element' %
1406                                 (attributeName,
1407                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
1408               
1409            attributeValues.append(attributeValue)
1410       
1411        attributeQuery = AttributeQuery()
1412        attributeQuery.version = SAMLVersion(attributeValues[0])
1413        if attributeQuery.version != SAMLVersion.VERSION_20:
1414            raise NotImplementedError("Parsing for %r is implemented for "
1415                                      "SAML version %s only; version %s is " 
1416                                      "not supported" % 
1417                                      (cls,
1418                                       SAMLVersion(SAMLVersion.VERSION_20),
1419                                       SAMLVersion(attributeQuery.version)))
1420           
1421        attributeQuery.issueInstant = SAMLDateTime.fromString(
1422                                                            attributeValues[1])
1423        attributeQuery.id = attributeValues[2]
1424       
1425        for childElem in elem:
1426            localName = QName.getLocalPart(childElem.tag)
1427            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1428                # Parse Issuer
1429                attributeQuery.issuer = IssuerElementTree.fromXML(childElem)
1430               
1431            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1432                # Parse Subject
1433                attributeQuery.subject = SubjectElementTree.fromXML(childElem)
1434           
1435            elif localName == Attribute.DEFAULT_ELEMENT_LOCAL_NAME:
1436                attribute = AttributeElementTree.fromXML(childElem)
1437                attributeQuery.attributes.append(attribute)
1438            else:
1439                raise XMLTypeParseError("Unrecognised AttributeQuery child "
1440                                          "element \"%s\"" % localName)
1441       
1442        return attributeQuery
1443       
1444   
1445class ResponseElementTree(Response):
1446    """Represent a SAML Response in XML using ElementTree"""
1447       
1448    @classmethod
1449    def toXML(cls, response, **attributeValueElementTreeFactoryKw):
1450        """Create an XML representation of the input SAML Response
1451        object
1452
1453        @type response: saml.saml2.core.Response
1454        @param response: SAML Response
1455        @type attributeValueElementTreeFactoryKw: dict
1456        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1457        factory
1458        @rtype: ElementTree.Element
1459        @return: Response as ElementTree XML element
1460        """
1461        if not isinstance(response, Response):
1462            raise TypeError("Expecting %r class, got %r" % (Response, 
1463                                                            type(response)))
1464         
1465        if response.id is None:
1466            raise TypeError("SAML Response id is not set")
1467         
1468        if response.issueInstant is None:
1469            raise TypeError("SAML Response issueInstant is not set")
1470       
1471        # TODO: Does inResponseTo have to be set?  This implementation
1472        # currently enforces this ...
1473        if response.inResponseTo is None:
1474            raise TypeError("SAML Response inResponseTo identifier is not set")
1475       
1476        issueInstant = SAMLDateTime.toString(response.issueInstant)
1477        attrib = {
1478            cls.ID_ATTRIB_NAME: response.id,
1479            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1480            cls.IN_RESPONSE_TO_ATTRIB_NAME: response.inResponseTo,
1481           
1482            # Nb. Version is a SAMLVersion instance and requires explicit cast
1483            cls.VERSION_ATTRIB_NAME: str(response.version)
1484        }
1485       
1486        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))       
1487        elem = ElementTree.Element(tag, **attrib)
1488       
1489        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1490                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1491
1492        # Issuer may be omitted: saml-profiles-2.0-os Section 4.1.4.2
1493        if response.issuer is not None: 
1494            issuerElem = IssuerElementTree.toXML(response.issuer)
1495            elem.append(issuerElem)
1496
1497        statusElem = StatusElementTree.toXML(response.status)       
1498        elem.append(statusElem)
1499
1500        for assertion in response.assertions:
1501            # Factory enables support for multiple attribute types
1502            assertionElem = AssertionElementTree.toXML(assertion,
1503                                        **attributeValueElementTreeFactoryKw)
1504            elem.append(assertionElem)
1505       
1506        return elem
1507
1508    @classmethod
1509    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
1510        """Parse ElementTree element into a SAML Response object
1511       
1512        @type elem: ElementTree.Element
1513        @param elem: XML element containing the Response
1514        @type attributeValueElementTreeFactoryKw: dict
1515        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1516        @rtype: saml.saml2.core.Response
1517        @return: Response object
1518        """
1519        if not ElementTree.iselement(elem):
1520            raise TypeError("Expecting %r input type for parsing; got %r" %
1521                            (ElementTree.Element, elem))
1522
1523        if QName.getLocalPart(elem.tag) != Response.DEFAULT_ELEMENT_LOCAL_NAME:
1524            raise XMLTypeParseError("No \"%s\" element found" %
1525                                      Response.DEFAULT_ELEMENT_LOCAL_NAME)
1526       
1527        # Unpack attributes from top-level element
1528        attributeValues = []
1529        for attributeName in (Response.VERSION_ATTRIB_NAME,
1530                              Response.ISSUE_INSTANT_ATTRIB_NAME,
1531                              Response.ID_ATTRIB_NAME,
1532                              Response.IN_RESPONSE_TO_ATTRIB_NAME):
1533            attributeValue = elem.attrib.get(attributeName)
1534            if attributeValue is None:
1535                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1536                                          'element' %
1537                                         (attributeName,
1538                                          Response.DEFAULT_ELEMENT_LOCAL_NAME))
1539               
1540            attributeValues.append(attributeValue)
1541       
1542        response = Response()
1543        response.version = SAMLVersion(attributeValues[0])
1544        if response.version != SAMLVersion.VERSION_20:
1545            raise NotImplementedError("Parsing for %r is implemented for "
1546                                      "SAML version %s only; version %s is " 
1547                                      "not supported" % 
1548                                      (cls,
1549                                       SAMLVersion(SAMLVersion.VERSION_20),
1550                                       SAMLVersion(response.version)))
1551           
1552        response.issueInstant = SAMLDateTime.fromString(attributeValues[1])
1553        response.id = attributeValues[2]
1554        response.inResponseTo = attributeValues[3]
1555       
1556        for childElem in elem:
1557            localName = QName.getLocalPart(childElem.tag)
1558            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1559                # Parse Issuer
1560                response.issuer = IssuerElementTree.fromXML(childElem)
1561           
1562            elif localName == Status.DEFAULT_ELEMENT_LOCAL_NAME:
1563                # Get status of response
1564                response.status = StatusElementTree.fromXML(childElem)
1565               
1566            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1567                # Parse Subject
1568                response.subject = SubjectElementTree.fromXML(childElem)
1569           
1570            elif localName == Assertion.DEFAULT_ELEMENT_LOCAL_NAME:
1571                assertion = AssertionElementTree.fromXML(childElem,
1572                                        **attributeValueElementTreeFactoryKw)
1573                response.assertions.append(assertion)
1574            else:
1575                raise XMLTypeParseError('Unrecognised Response child '
1576                                          'element "%s"' % localName)
1577       
1578        return response
1579
Note: See TracBrowser for help on using the repository browser.