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

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

Working ElementTree serialisation/deserialisation for AuthzDecisionQuery?

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