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

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

Working serialised Response with AuthzDecisionStatement? in unit tests.

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, Evidence,
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            authzDecisionStatementElem = \
374                AuthzDecisionStatementElementTree.toXML(authzDecisionStatement)
375            elem.append(authzDecisionStatementElem)
376           
377        for attributeStatement in assertion.attributeStatements:
378            attributeStatementElem = AttributeStatementElementTree.toXML(
379                                        attributeStatement,
380                                        **attributeValueElementTreeFactoryKw)
381            elem.append(attributeStatementElem)
382       
383        return elem
384
385    @classmethod
386    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
387        """Parse an ElementTree representation of an Assertion into an
388        Assertion object
389       
390        @type elem: ElementTree.Element
391        @param elem: ElementTree element containing the assertion
392        @type attributeValueElementTreeFactoryKw: dict
393        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
394        @rtype: saml.saml2.core.Assertion
395        @return: Assertion object"""
396        if not ElementTree.iselement(elem):
397            raise TypeError("Expecting %r input type for parsing; got %r" %
398                            (ElementTree.Element, elem))
399
400        localName = QName.getLocalPart(elem.tag)
401        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
402            raise XMLTypeParseError("No \"%s\" element found" %
403                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
404       
405       
406        # Unpack attributes from top-level element
407        attributeValues = []
408        for attributeName in (cls.VERSION_ATTRIB_NAME,
409                              cls.ISSUE_INSTANT_ATTRIB_NAME,
410                              cls.ID_ATTRIB_NAME):
411            attributeValue = elem.attrib.get(attributeName)
412            if attributeValue is None:
413                raise XMLTypeParseError('No "%s" attribute found in "%s" '
414                                          'element' %
415                                          (attributeName,
416                                           cls.DEFAULT_ELEMENT_LOCAL_NAME))
417               
418            attributeValues.append(attributeValue)
419       
420        assertion = cls()
421        assertion.version = SAMLVersion(attributeValues[0])
422        if assertion.version != SAMLVersion.VERSION_20:
423            raise NotImplementedError("Parsing for %r is implemented for "
424                                      "SAML version %s only; version %s is " 
425                                      "not supported" % 
426                                      (cls,
427                                       SAMLVersion(SAMLVersion.VERSION_20),
428                                       SAMLVersion(assertion.version)))
429           
430        assertion.issueInstant = SAMLDateTime.fromString(attributeValues[1])
431        assertion.id = attributeValues[2]
432       
433        for childElem in elem:
434            localName = QName.getLocalPart(childElem.tag)
435            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
436                # Parse Issuer
437                assertion.issuer = IssuerElementTree.fromXML(childElem)
438               
439            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
440                # Parse subject
441                assertion.subject = SubjectElementTree.fromXML(childElem)
442               
443            elif localName == Advice.DEFAULT_ELEMENT_LOCAL_NAME:
444                raise NotImplementedError("Assertion Advice parsing is not "
445                                          "implemented")
446               
447            elif localName == Conditions.DEFAULT_ELEMENT_LOCAL_NAME:
448                assertion.conditions = ConditionsElementTree.fromXML(childElem)
449       
450            elif localName == AuthnStatement.DEFAULT_ELEMENT_LOCAL_NAME:
451                raise NotImplementedError("Assertion Authentication Statement "
452                                          "parsing is not implemented")
453       
454            elif localName == AuthzDecisionStatement.DEFAULT_ELEMENT_LOCAL_NAME:
455                authzDecisionStatement = \
456                    AuthzDecisionStatementElementTree.fromXML(childElem)
457                assertion.authzDecisionStatements.append(authzDecisionStatement)
458           
459            elif localName == AttributeStatement.DEFAULT_ELEMENT_LOCAL_NAME:
460                attributeStatement = AttributeStatementElementTree.fromXML(
461                                        childElem,
462                                        **attributeValueElementTreeFactoryKw)
463                assertion.attributeStatements.append(attributeStatement)
464            else:
465                raise XMLTypeParseError('Assertion child element name "%s" '
466                                        'not recognised' % localName)
467       
468        return assertion
469
470 
471class AttributeStatementElementTree(AttributeStatement):
472    """ElementTree XML representation of AttributeStatement"""
473   
474    @classmethod
475    def toXML(cls, attributeStatement, **attributeValueElementTreeFactoryKw):
476        """Make a tree of a XML elements based on the attribute statement
477       
478        @type assertion: saml.saml2.core.AttributeStatement
479        @param assertion: Attribute Statement to be represented as an
480        ElementTree Element
481        @type attributeValueElementTreeFactoryKw: dict
482        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
483        factory
484        @rtype: ElementTree.Element
485        @return: ElementTree Element
486        """
487        if not isinstance(attributeStatement, AttributeStatement):
488            raise TypeError("Expecting %r type got: %r" % (AttributeStatement, 
489                                                           attributeStatement))
490           
491        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
492        elem = ElementTree.Element(tag)
493        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
494                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
495
496        for attribute in attributeStatement.attributes:
497            # Factory enables support for multiple attribute types
498            attributeElem = AttributeElementTree.toXML(attribute,
499                                        **attributeValueElementTreeFactoryKw)
500            elem.append(attributeElem)
501       
502        return elem
503   
504    @classmethod
505    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
506        """Parse an ElementTree SAML AttributeStatement element into an
507        AttributeStatement object
508       
509        @type elem: ElementTree.Element
510        @param elem: ElementTree element containing the AttributeStatement
511        @type attributeValueElementTreeFactoryKw: dict
512        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
513        factory
514        @rtype: saml.saml2.core.AttributeStatement
515        @return: Attribute Statement"""
516       
517        if not ElementTree.iselement(elem):
518            raise TypeError("Expecting %r input type for parsing; got %r" %
519                            (ElementTree.Element, elem))
520
521        localName = QName.getLocalPart(elem.tag)
522        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
523            raise XMLTypeParseError("No \"%s\" element found" %
524                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
525       
526       
527        attributeStatement = AttributeStatement()
528
529        for childElem in elem:
530            # Factory enables support for multiple attribute types
531            attribute = AttributeElementTree.fromXML(childElem,
532                                        **attributeValueElementTreeFactoryKw)
533            attributeStatement.attributes.append(attribute)
534       
535        return attributeStatement
536
537 
538class AuthzDecisionStatementElementTree(AuthzDecisionStatement):
539    """ElementTree XML representation of AuthzDecisionStatement"""
540   
541    @classmethod
542    def toXML(cls, authzDecisionStatement):
543        """Make a tree of a XML elements based on the authzDecision statement
544       
545        @type assertion: saml.saml2.core.AuthzDecisionStatement
546        @param assertion: AuthzDecision Statement to be represented as an
547        ElementTree Element
548        factory
549        @rtype: ElementTree.Element
550        @return: ElementTree Element
551        """
552        if not isinstance(authzDecisionStatement, AuthzDecisionStatement):
553            raise TypeError("Expecting %r type got: %r" % 
554                            (AuthzDecisionStatement, authzDecisionStatement))
555         
556        if not authzDecisionStatement.resource:
557            raise AttributeError("Resource for AuthzDecisionStatement is not "
558                                 "set")
559             
560        attrib = {
561            cls.DECISION_ATTRIB_NAME: authzDecisionStatement.decision,
562            cls.RESOURCE_ATTRIB_NAME: authzDecisionStatement.resource
563        }
564           
565        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
566        elem = ElementTree.Element(tag, **attrib)
567        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
568                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
569
570        for action in authzDecisionStatement.actions:
571            # Factory enables support for multiple authzDecision types
572            actionElem = ActionElementTree.toXML(action)
573            elem.append(actionElem)
574       
575        if (authzDecisionStatement.evidence and 
576            len(authzDecisionStatement.evidence.values) > 0):
577            raise NotImplementedError("authzDecisionStatementElementTree does "
578                                      "not currently support the Evidence type")
579           
580        return elem
581   
582    @classmethod
583    def fromXML(cls, elem, **authzDecisionValueElementTreeFactoryKw):
584        """Parse an ElementTree SAML AuthzDecisionStatement element into an
585        AuthzDecisionStatement object
586       
587        @type elem: ElementTree.Element
588        @param elem: ElementTree element containing the AuthzDecisionStatement
589        @type authzDecisionValueElementTreeFactoryKw: dict
590        @param authzDecisionValueElementTreeFactoryKw: keywords for AuthzDecisionValue
591        factory
592        @rtype: saml.saml2.core.AuthzDecisionStatement
593        @return: AuthzDecision Statement"""
594       
595        if not ElementTree.iselement(elem):
596            raise TypeError("Expecting %r input type for parsing; got %r" %
597                            (ElementTree.Element, elem))
598
599        localName = QName.getLocalPart(elem.tag)
600        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
601            raise XMLTypeParseError("No \"%s\" element found" %
602                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
603       
604       
605        authzDecisionStatement = cls()
606
607        for childElem in elem:
608            localName = QName.getLocalPart(childElem.tag)
609           
610            if localName == Action.DEFAULT_ELEMENT_LOCAL_NAME:
611                action = ActionElementTree.fromXML(childElem)
612                authzDecisionStatement.actions.append(action)
613               
614            elif localName == Evidence.DEFAULT_ELEMENT_LOCAL_NAME:
615                raise NotImplementedError("XML parse of %s element is not "
616                                          "implemented" %
617                                          Evidence.DEFAULT_ELEMENT_LOCAL_NAME)
618            else:
619                raise XMLTypeParseError("AuthzDecisionStatement child element "
620                                        "name %r not recognised" % localName)
621       
622        return authzDecisionStatement
623
624
625class AttributeElementTree(Attribute):
626    """ElementTree XML representation of SAML Attribute object.  Extend
627    to make Attribute types""" 
628
629    @classmethod
630    def toXML(cls, attribute, **attributeValueElementTreeFactoryKw):
631        """Make a tree of a XML elements based on the Attribute
632       
633        @type assertion: saml.saml2.core.Attribute
634        @param assertion: Attribute to be represented as an ElementTree Element
635        @type attributeValueElementTreeFactoryKw: dict
636        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
637        factory
638        @rtype: ElementTree.Element
639        @return: ElementTree Element
640        """
641        if not isinstance(attribute, Attribute):
642            raise TypeError("Expecting %r type got: %r"%(Attribute, attribute))
643       
644        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))   
645        elem = ElementTree.Element(tag)
646        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
647                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
648       
649        if attribute.friendlyName:
650            elem.set(cls.FRIENDLY_NAME_ATTRIB_NAME, attribute.friendlyName) 
651             
652        if attribute.name:
653            elem.set(cls.NAME_ATTRIB_NAME, attribute.name)
654       
655        if attribute.nameFormat:
656            elem.set(cls.NAME_FORMAT_ATTRIB_NAME, attribute.nameFormat)
657
658        for attributeValue in attribute.attributeValues:
659            factory = AttributeValueElementTreeFactory(
660                                        **attributeValueElementTreeFactoryKw)
661           
662            attributeValueElementTree = factory(attributeValue)
663           
664            attributeValueElem=attributeValueElementTree.toXML(attributeValue)
665            elem.append(attributeValueElem)
666           
667        return elem
668 
669    @classmethod
670    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
671        """Parse ElementTree element into a SAML Attribute object
672       
673        @type elem: ElementTree.Element
674        @param elem: Attribute as ElementTree XML element
675        @type attributeValueElementTreeFactoryKw: dict
676        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
677        factory
678        @rtype: saml.saml2.core.Attribute
679        @return: SAML Attribute
680        """
681        if not ElementTree.iselement(elem):
682            raise TypeError("Expecting %r input type for parsing; got %r" %
683                            (ElementTree.Element, elem))
684
685        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
686            raise XMLTypeParseError("No \"%s\" element found" %
687                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
688           
689        attribute = Attribute()
690           
691        # Name is mandatory in the schema
692        name = elem.attrib.get(cls.NAME_ATTRIB_NAME)
693        if name is None:
694            raise XMLTypeParseError('No "%s" attribute found in the "%s" '
695                                    'element' %
696                                    (cls.NAME_ATTRIB_NAME,
697                                     cls.DEFAULT_ELEMENT_LOCAL_NAME))
698        attribute.name = name
699           
700        friendlyName = elem.attrib.get(cls.FRIENDLY_NAME_ATTRIB_NAME)
701        if friendlyName is not None:
702            attribute.friendlyName = friendlyName
703           
704        nameFormat = elem.attrib.get(cls.NAME_FORMAT_ATTRIB_NAME)   
705        if nameFormat is not None:
706            attribute.nameFormat = nameFormat
707       
708        # Factory to handle the different Attribute Value types
709        factory = AttributeValueElementTreeFactory(
710                                        **attributeValueElementTreeFactoryKw)
711
712        for childElem in elem:
713            localName = QName.getLocalPart(childElem.tag)
714            if localName != AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME:
715                raise XMLTypeParseError('Expecting "%s" element; found "%s"'%
716                                    (AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME,
717                                     localName))
718                           
719            attributeValueElementTreeClass = factory(childElem)
720            attributeValue = attributeValueElementTreeClass.fromXML(childElem)
721            attribute.attributeValues.append(attributeValue)
722       
723        return attribute
724       
725   
726class AttributeValueElementTreeBase(AttributeValue):
727    """Base class ElementTree XML representation of SAML Attribute Value""" 
728   
729    @classmethod
730    def toXML(cls, attributeValue):
731        """Make a tree of a XML elements based on the Attribute value
732       
733        @type assertion: saml.saml2.core.Assertion
734        @param assertion: Assertion to be represented as an ElementTree Element
735        @rtype: ElementTree.Element
736        @return: ElementTree Element
737        """
738        if not isinstance(attributeValue, AttributeValue):
739            raise TypeError("Expecting %r type got: %r" % (AttributeValue, 
740                                                           attributeValue))
741           
742        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))   
743        elem = ElementTree.Element(tag)
744        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
745                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
746
747        return elem
748
749
750class XSStringAttributeValueElementTree(AttributeValueElementTreeBase,
751                                        XSStringAttributeValue):
752    """ElementTree XML representation of SAML String type Attribute Value""" 
753   
754    @classmethod
755    def toXML(cls, attributeValue):
756        """Create an XML representation of the input SAML Attribute Value
757       
758        @type assertion: saml.saml2.core.XSStringAttributeValue
759        @param assertion: xs:string to be represented as an ElementTree Element
760        @rtype: ElementTree.Element
761        @return: ElementTree Element
762        """
763        elem = AttributeValueElementTreeBase.toXML(attributeValue)
764       
765        if not isinstance(attributeValue, XSStringAttributeValue):
766            raise TypeError("Expecting %r type got: %r" % 
767                            (XSStringAttributeValue, attributeValue)) 
768       
769        # Have to explicitly add namespace declaration here rather use
770        # ElementTree._namespace_map because the prefixes are used for
771        # attributes not element names       
772        elem.set("%s:%s" % (SAMLConstants.XMLNS_PREFIX, 
773                            SAMLConstants.XSD_PREFIX),
774                 SAMLConstants.XSD_NS)
775                                   
776        elem.set("%s:%s" % (SAMLConstants.XMLNS_PREFIX, 
777                            SAMLConstants.XSI_PREFIX),
778                 SAMLConstants.XSI_NS)
779       
780        elem.set("%s:%s" % (SAMLConstants.XSI_PREFIX, 'type'), 
781                 "%s:%s" % (SAMLConstants.XSD_PREFIX, 
782                            cls.TYPE_LOCAL_NAME))
783
784        elem.text = attributeValue.value
785
786        return elem
787
788    @classmethod
789    def fromXML(cls, elem):
790        """Parse ElementTree xs:string element into a SAML
791        XSStringAttributeValue object
792       
793        @type elem: ElementTree.Element
794        @param elem: Attribute value as ElementTree XML element
795        @rtype: saml.saml2.core.AttributeValue
796        @return: SAML Attribute value
797        """
798        if not ElementTree.iselement(elem):
799            raise TypeError("Expecting %r input type for parsing; got %r" %
800                            (ElementTree.Element, elem))
801
802        localName = QName.getLocalPart(elem.tag)
803        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
804            raise XMLTypeParseError("No \"%s\" element found" %
805                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
806       
807        # Parse the attribute type checking that it is set to the expected
808        # string type
809        typeQName = QName(SAMLConstants.XSI_NS, tag='type')
810       
811        typeValue = elem.attrib.get(str(typeQName), '')
812        typeValueLocalName = typeValue.split(':')[-1]
813        if typeValueLocalName != cls.TYPE_LOCAL_NAME:
814            raise XMLTypeParseError('Expecting "%s" type; got "%s"' %
815                                      (cls.TYPE_LOCAL_NAME,
816                                       typeValueLocalName))
817       
818        # Update namespace map as an XSI type has been referenced.  This will
819        # ensure the correct prefix is applied if it is re-serialised.
820        ElementTree._namespace_map[SAMLConstants.XSI_NS
821                                   ] = SAMLConstants.XSI_PREFIX
822                                     
823        attributeValue = XSStringAttributeValue()
824        if elem.text is not None:
825            attributeValue.value = elem.text.strip()
826
827        return attributeValue
828
829
830class AttributeValueElementTreeFactory(object):
831    """Class factory for AttributeValue ElementTree classes.  These classes are
832    used to represent SAML Attribute value types
833   
834    @type toXMLTypeMap: dict
835    @cvar toXMLTypeMap: mapping between SAML AttributeValue class and its
836    ElementTree handler class
837    @type toSAMLTypeMap: dict
838    @cvar toSAMLTypeMap: mapping between SAML AttributeValue string identifier and
839    its ElementTree handler class
840    """
841    toXMLTypeMap = {
842        XSStringAttributeValue: XSStringAttributeValueElementTree
843    }
844
845    def xsstringMatch(elem):
846        """Match function for xs:string type attribute.
847        @type elem: ElementTree.Element
848        @param elem: Attribute Value element to be checked
849        @rtype: XSStringAttributeValueElementTree/None
850        @return: Parsing class if this element is an xs:string Attribute Value,
851        None otherwise.
852        """
853        # Iterate through the attributes searching for a type attribute set to
854        # xs:string
855        for attribName, attribVal in elem.attrib.items():
856            qname = QName(attribName)
857            if qname.localPart == "type":
858                typeLocalName = attribVal.split(':')[-1]
859               
860                if typeLocalName == XSStringAttributeValue.TYPE_LOCAL_NAME:
861                    return XSStringAttributeValueElementTree
862                else:
863                    return None
864               
865        # No type attribute was found for this Attribute element
866        return None
867       
868    toSAMLTypeMap = [xsstringMatch]
869    xsstringMatch = staticmethod(toSAMLTypeMap[0])
870   
871    def __init__(self, customToXMLTypeMap={}, customToSAMLTypeMap=[]): 
872        """Set-up a SAML class to ElementTree mapping
873        @type customToXMLTypeMap: dict
874        @param customToXMLTypeMap: mapping for custom SAML AttributeValue
875        classes to their respective ElementTree based representations.  This
876        appends to self.__toXMLTypeMap
877        @type customToSAMLTypeMap: dict
878        @param customToSAMLTypeMap: string ID based mapping for custom SAML
879        AttributeValue classes to their respective ElementTree based
880        representations.  As with customToXMLTypeMap, this appends to
881        to the respective self.__toSAMLTypeMap
882        """
883        self.__toXMLTypeMap = AttributeValueElementTreeFactory.toXMLTypeMap
884        if not isinstance(customToXMLTypeMap, dict):
885            raise TypeError('Expecting dict type for "customToXMLTypeMap"')
886
887        for samlClass, etreeClass in customToXMLTypeMap.items(): 
888            if not issubclass(samlClass, AttributeValue):
889                raise TypeError("Input custom class must be derived from %r, "
890                                "got %r instead" % (Attribute, samlClass))
891               
892            self.__toXMLTypeMap[samlClass] = etreeClass
893
894        if not isinstance(customToSAMLTypeMap, (list, tuple)):
895            raise TypeError('Expecting list or tuple type for '
896                            '"customToSAMLTypeMap"')
897       
898        self.__toSAMLTypeMap = AttributeValueElementTreeFactory.toSAMLTypeMap[:]
899        for func in customToSAMLTypeMap:
900            if not callable(func):
901                raise TypeError('"customToSAMLTypeMap" items must be callable')
902           
903        self.__toSAMLTypeMap += customToSAMLTypeMap
904
905    def __call__(self, input):
906        """Create an ElementTree object based on the Attribute class type
907        passed in
908       
909        @type input: saml.saml2.core.AttributeValue or basestring
910        @param input: pass an AttributeValue derived type or a string.  If
911        an AttributeValue type, then self.__toXMLTypeMap is checked for a matching
912        AttributeValue class entry, if a string is passed, self.__toSAMLTypeMap is
913        checked for a matching string ID.  In both cases, if a match is
914        found an ElementTree class is returned which can render or parse
915        the relevant AttributeValue class
916        """
917        if isinstance(input, AttributeValue):
918            XMLTypeClass = self.__toXMLTypeMap.get(input.__class__)
919            if XMLTypeClass is None:
920                raise UnknownAttrProfile("no matching XMLType class "
921                                         "representation for class %r" % 
922                                         input.__class__)
923               
924        elif ElementTree.iselement(input):
925            XMLTypeClasses = []
926            for matchFunc in self.__toSAMLTypeMap:
927                cls = matchFunc(input)
928                if cls is None:
929                    continue
930                elif issubclass(cls, AttributeValue):
931                    XMLTypeClasses.append(cls)
932                else:
933                    raise TypeError("Expecting AttributeValue derived type "
934                                    "for XML class; got %r" % cls)
935           
936            nXMLTypeClasses = len(XMLTypeClasses)
937            if nXMLTypeClasses == 0:
938                raise UnknownAttrProfile("no matching XMLType class "
939                                         "representation for SAML "
940                                         "AttributeValue type %r" % input)
941            elif nXMLTypeClasses > 1:
942                raise TypeError("Multiple XMLType classes %r matched for "
943                                "for SAML AttributeValue type %r" % 
944                                (XMLTypeClasses, input)) 
945                   
946            XMLTypeClass = XMLTypeClasses[0]           
947        else:
948            raise TypeError("Expecting %r class got %r" % (AttributeValue, 
949                                                           type(input)))
950        return XMLTypeClass
951   
952
953class IssuerElementTree(Issuer):
954    """Represent a SAML Issuer element in XML using ElementTree"""
955   
956    @classmethod
957    def toXML(cls, issuer):
958        """Create an XML representation of the input SAML issuer object"""
959        if not isinstance(issuer, Issuer):
960            raise TypeError("Expecting %r class got %r" % (Issuer, 
961                                                           type(issuer)))
962           
963        # Issuer format may be omitted from a response: saml-profiles-2.0-os,
964        # Section 4.1.4.2
965        attrib = {}
966        if issuer.format is not None:
967            attrib[cls.FORMAT_ATTRIB_NAME] = issuer.format
968       
969        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
970        elem = ElementTree.Element(tag, **attrib)
971        ElementTree._namespace_map[issuer.qname.namespaceURI
972                                   ] = issuer.qname.prefix
973                                   
974        elem.text = issuer.value
975
976        return elem
977
978    @classmethod
979    def fromXML(cls, elem):
980        """Parse ElementTree element into a SAML Issuer instance
981       
982        @type elem: ElementTree.Element
983        @param elem: ElementTree element containing the assertion
984        @rtype: saml.saml2.core.Issuer
985        @return: Assertion object"""
986        if not ElementTree.iselement(elem):
987            raise TypeError("Expecting %r input type for parsing; got %r" %
988                            (ElementTree.Element, elem))
989
990        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
991            raise XMLTypeParseError('No "%s" element found' %
992                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
993           
994        issuerFormat = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
995        issuer = Issuer()
996       
997        # Issuer format may be omitted from a response: saml-profiles-2.0-os,
998        # Section 4.1.4.2
999        if issuerFormat is not None:
1000            issuer.format = issuerFormat
1001       
1002        issuer.value = elem.text.strip() 
1003       
1004        return issuer
1005
1006       
1007class NameIdElementTree(NameID):
1008    """Represent a SAML Name Identifier in XML using ElementTree"""
1009   
1010    @classmethod
1011    def toXML(cls, nameID):
1012        """Create an XML representation of the input SAML Name Identifier
1013        object
1014        @type nameID: saml.saml2.core.NameID
1015        @param nameID: SAML name ID
1016        @rtype: ElementTree.Element
1017        @return: Name ID as ElementTree XML element"""
1018       
1019        if not isinstance(nameID, NameID):
1020            raise TypeError("Expecting %r class got %r" % (NameID, 
1021                                                           type(nameID)))
1022        attrib = {
1023            cls.FORMAT_ATTRIB_NAME: nameID.format
1024        }
1025        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1026        elem = ElementTree.Element(tag, **attrib)
1027       
1028        ElementTree._namespace_map[nameID.qname.namespaceURI
1029                                   ] = nameID.qname.prefix
1030       
1031        elem.text = nameID.value
1032
1033        return elem
1034
1035    @classmethod
1036    def fromXML(cls, elem):
1037        """Parse ElementTree element into a SAML NameID object
1038       
1039        @type elem: ElementTree.Element
1040        @param elem: Name ID as ElementTree XML element
1041        @rtype: saml.saml2.core.NameID
1042        @return: SAML Name ID
1043        """
1044        if not ElementTree.iselement(elem):
1045            raise TypeError("Expecting %r input type for parsing; got %r" %
1046                            (ElementTree.Element, elem))
1047
1048        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1049            raise XMLTypeParseError("No \"%s\" element found" %
1050                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1051           
1052        format = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
1053        if format is None:
1054            raise XMLTypeParseError('No "%s" attribute found in "%s" '
1055                                    'element' %
1056                                    (cls.FORMAT_ATTRIB_NAME,
1057                                     cls.DEFAULT_ELEMENT_LOCAL_NAME))
1058        nameID = NameID()
1059        nameID.format = format
1060        nameID.value = elem.text.strip() 
1061       
1062        return nameID
1063
1064
1065class SubjectElementTree(Subject):
1066    """Represent a SAML Subject in XML using ElementTree"""
1067   
1068    @classmethod
1069    def toXML(cls, subject):
1070        """Create an XML representation of the input SAML subject object
1071        @type subject: saml.saml2.core.Subject
1072        @param subject: SAML subject
1073        @rtype: ElementTree.Element
1074        @return: subject as ElementTree XML element
1075        """
1076        if not isinstance(subject, Subject):
1077            raise TypeError("Expecting %r class got %r" % (Subject, 
1078                                                           type(subject)))
1079           
1080        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
1081        elem = ElementTree.Element(tag)
1082       
1083        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1084                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1085
1086           
1087        nameIdElem = NameIdElementTree.toXML(subject.nameID)
1088        elem.append(nameIdElem)
1089       
1090        return elem
1091
1092    @classmethod
1093    def fromXML(cls, elem):
1094        """Parse ElementTree element into a SAML Subject object
1095       
1096        @type elem: ElementTree.Element
1097        @param elem: subject as ElementTree XML element
1098        @rtype: saml.saml2.core.Subject
1099        @return: SAML subject
1100        """
1101        if not ElementTree.iselement(elem):
1102            raise TypeError("Expecting %r input type for parsing; got %r" %
1103                            (ElementTree.Element, elem))
1104
1105        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1106            raise XMLTypeParseError("No \"%s\" element found" %
1107                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
1108           
1109        if len(elem) != 1:
1110            raise XMLTypeParseError("Expecting single Name ID child element "
1111                                      "for SAML Subject element")
1112           
1113        subject = Subject()
1114        subject.nameID = NameIdElementTree.fromXML(elem[0])
1115       
1116        return subject
1117
1118       
1119class StatusCodeElementTree(StatusCode):
1120    """Represent a SAML Status Code in XML using ElementTree"""
1121   
1122    @classmethod
1123    def toXML(cls, statusCode):
1124        """Create an XML representation of the input SAML Name Status Code
1125       
1126        @type statusCode: saml.saml2.core.StatusCode
1127        @param statusCode: SAML Status Code
1128        @rtype: ElementTree.Element
1129        @return: Status Code as ElementTree XML element"""
1130       
1131        if not isinstance(statusCode, StatusCode):
1132            raise TypeError("Expecting %r class got %r" % (StatusCode, 
1133                                                           type(statusCode)))
1134           
1135        attrib = {
1136            cls.VALUE_ATTRIB_NAME: statusCode.value
1137        }
1138        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1139        elem = ElementTree.Element(tag, **attrib)
1140       
1141        ElementTree._namespace_map[statusCode.qname.namespaceURI
1142                                   ] = statusCode.qname.prefix
1143
1144        return elem
1145
1146    @classmethod
1147    def fromXML(cls, elem):
1148        """Parse ElementTree element into a SAML StatusCode object
1149       
1150        @type elem: ElementTree.Element
1151        @param elem: Status Code as ElementTree XML element
1152        @rtype: saml.saml2.core.StatusCode
1153        @return: SAML Status Code
1154        """
1155        if not ElementTree.iselement(elem):
1156            raise TypeError("Expecting %r input type for parsing; got %r" %
1157                            (ElementTree.Element, elem))
1158
1159        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1160            raise XMLTypeParseError('No "%s" element found' %
1161                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1162           
1163        statusCode = StatusCode()
1164           
1165        value = elem.attrib.get(cls.VALUE_ATTRIB_NAME)
1166        if value is None:
1167            raise XMLTypeParseError('No "%s" attribute found in "%s" element' %
1168                                    (cls.VALUE_ATTRIB_NAME,
1169                                     cls.DEFAULT_ELEMENT_LOCAL_NAME))
1170        statusCode.value = value
1171       
1172        return statusCode
1173
1174       
1175class StatusMessageElementTree(StatusMessage):
1176    """Represent a SAML Status Message in XML using ElementTree"""
1177   
1178    @classmethod
1179    def toXML(cls, statusMessage):
1180        """Create an XML representation of the input SAML Name Status Message
1181       
1182        @type statusMessage: saml.saml2.core.StatusMessage
1183        @param statusMessage: SAML Status Message
1184        @rtype: ElementTree.Element
1185        @return: Status Code as ElementTree XML element"""
1186       
1187        if not isinstance(statusMessage, StatusMessage):
1188            raise TypeError("Expecting %r class got %r" % (StatusMessage, 
1189                                                           type(statusMessage)))
1190           
1191        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1192        elem = ElementTree.Element(tag)
1193       
1194        ElementTree._namespace_map[statusMessage.qname.namespaceURI
1195                                   ] = statusMessage.qname.prefix
1196       
1197        elem.text = statusMessage.value
1198
1199        return elem
1200
1201    @classmethod
1202    def fromXML(cls, elem):
1203        """Parse ElementTree element into a SAML StatusMessage object
1204       
1205        @type elem: ElementTree.Element
1206        @param elem: Status Code as ElementTree XML element
1207        @rtype: saml.saml2.core.StatusMessage
1208        @return: SAML Status Message
1209        """
1210        if not ElementTree.iselement(elem):
1211            raise TypeError("Expecting %r input type for parsing; got %r" %
1212                            (ElementTree.Element, elem))
1213
1214        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1215            raise XMLTypeParseError('No "%s" element found' %
1216                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1217           
1218        statusMessage = StatusMessage()
1219        if elem.text is not None:
1220            statusMessage.value = elem.text.strip() 
1221       
1222        return statusMessage
1223
1224
1225class StatusElementTree(Status):
1226    """Represent a SAML Status in XML using ElementTree"""
1227   
1228    @classmethod
1229    def toXML(cls, status):
1230        """Create an XML representation of the input SAML subject object
1231        @type subject: saml.saml2.core.Status
1232        @param subject: SAML subject
1233        @rtype: ElementTree.Element
1234        @return: subject as ElementTree XML element
1235        """
1236        if not isinstance(status, Status):
1237            raise TypeError("Expecting %r class got %r" % (status, 
1238                                                           type(Status)))
1239           
1240        tag = str(QName.fromGeneric(Status.DEFAULT_ELEMENT_NAME)) 
1241        elem = ElementTree.Element(tag)
1242       
1243        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1244                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1245       
1246        statusCodeElem = StatusCodeElementTree.toXML(status.statusCode)
1247        elem.append(statusCodeElem)
1248       
1249        # Status message is optional
1250        if status.statusMessage is not None and \
1251           status.statusMessage.value is not None:
1252            statusMessageElem = StatusMessageElementTree.toXML(
1253                                                        status.statusMessage)
1254            elem.append(statusMessageElem)
1255       
1256        if status.statusDetail is not None:
1257            raise NotImplementedError("StatusDetail XML serialisation is not "
1258                                      "implemented")
1259           
1260        return elem
1261
1262    @classmethod
1263    def fromXML(cls, elem):
1264        """Parse ElementTree element into a SAML Status object
1265       
1266        @type elem: ElementTree.Element
1267        @param elem: subject as ElementTree XML element
1268        @rtype: saml.saml2.core.Status
1269        @return: SAML subject
1270        """
1271        if not ElementTree.iselement(elem):
1272            raise TypeError("Expecting %r input type for parsing; got %r" %
1273                            (ElementTree.Element, elem))
1274
1275        if QName.getLocalPart(elem.tag) != Status.DEFAULT_ELEMENT_LOCAL_NAME:
1276            raise XMLTypeParseError('No "%s" element found' %
1277                                      Status.DEFAULT_ELEMENT_LOCAL_NAME)
1278           
1279        if len(elem) < 1:
1280            raise XMLTypeParseError("Expecting a StatusCode child element for "
1281                                    "SAML Status element")
1282           
1283        status = Status()
1284        for childElem in elem:
1285            localName = QName.getLocalPart(childElem.tag)
1286            if localName == StatusCode.DEFAULT_ELEMENT_LOCAL_NAME:
1287                status.statusCode = StatusCodeElementTree.fromXML(childElem)
1288               
1289            elif localName == StatusMessage.DEFAULT_ELEMENT_LOCAL_NAME:
1290                status.statusMessage = StatusMessageElementTree.fromXML(
1291                                                                childElem)
1292            elif localName == StatusDetail.DEFAULT_ELEMENT_LOCAL_NAME:
1293                raise NotImplementedError("XML parse of %s element is not "
1294                                    "implemented" %
1295                                    StatusDetail.DEFAULT_ELEMENT_LOCAL_NAME)
1296            else:
1297                raise XMLTypeParseError("Status child element name %r not "
1298                                        "recognised" % localName)
1299       
1300        return status
1301   
1302   
1303class AttributeQueryElementTree(AttributeQuery):
1304    """Represent a SAML Attribute Query in XML using ElementTree"""
1305       
1306    @classmethod
1307    def toXML(cls, attributeQuery, **attributeValueElementTreeFactoryKw):
1308        """Create an XML representation of the input SAML Attribute Query
1309        object
1310
1311        @type attributeQuery: saml.saml2.core.AttributeQuery
1312        @param attributeQuery: SAML Attribute Query
1313        @type attributeValueElementTreeFactoryKw: dict
1314        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1315        factory
1316        @rtype: ElementTree.Element
1317        @return: Attribute Query as ElementTree XML element
1318        """
1319        if not isinstance(attributeQuery, AttributeQuery):
1320            raise TypeError("Expecting %r class got %r" % (AttributeQuery, 
1321                                                        type(attributeQuery)))
1322           
1323       
1324        issueInstant = SAMLDateTime.toString(attributeQuery.issueInstant)
1325        attrib = {
1326            cls.ID_ATTRIB_NAME: attributeQuery.id,
1327            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1328           
1329            # Nb. Version is a SAMLVersion instance and requires explicit cast
1330            cls.VERSION_ATTRIB_NAME: str(attributeQuery.version)
1331        }
1332       
1333        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1334        elem = ElementTree.Element(tag, **attrib)
1335       
1336        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1337                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1338       
1339        issuerElem = IssuerElementTree.toXML(attributeQuery.issuer)
1340        elem.append(issuerElem)
1341
1342        subjectElem = SubjectElementTree.toXML(attributeQuery.subject)
1343        elem.append(subjectElem)
1344
1345        for attribute in attributeQuery.attributes:
1346            # Factory enables support for multiple attribute types
1347            attributeElem = AttributeElementTree.toXML(attribute,
1348                                        **attributeValueElementTreeFactoryKw)
1349            elem.append(attributeElem)
1350       
1351        return elem
1352
1353    @classmethod
1354    def fromXML(cls, elem):
1355        """Parse ElementTree element into a SAML AttributeQuery object
1356       
1357        @type elem: ElementTree.Element
1358        @param elem: XML element containing the AttributeQuery
1359        @rtype: saml.saml2.core.AttributeQuery
1360        @return: AttributeQuery object
1361        """
1362        if not ElementTree.iselement(elem):
1363            raise TypeError("Expecting %r input type for parsing; got %r" %
1364                            (ElementTree.Element, elem))
1365
1366        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1367            raise XMLTypeParseError("No \"%s\" element found" %
1368                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1369       
1370        # Unpack attributes from top-level element
1371        attributeValues = []
1372        for attributeName in (cls.VERSION_ATTRIB_NAME,
1373                              cls.ISSUE_INSTANT_ATTRIB_NAME,
1374                              cls.ID_ATTRIB_NAME):
1375            attributeValue = elem.attrib.get(attributeName)
1376            if attributeValue is None:
1377                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1378                                 'element' %
1379                                 (attributeName,
1380                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
1381               
1382            attributeValues.append(attributeValue)
1383       
1384        attributeQuery = AttributeQuery()
1385        attributeQuery.version = SAMLVersion(attributeValues[0])
1386        if attributeQuery.version != SAMLVersion.VERSION_20:
1387            raise NotImplementedError("Parsing for %r is implemented for "
1388                                      "SAML version %s only; version %s is " 
1389                                      "not supported" % 
1390                                      (cls,
1391                                       SAMLVersion(SAMLVersion.VERSION_20),
1392                                       SAMLVersion(attributeQuery.version)))
1393           
1394        attributeQuery.issueInstant = SAMLDateTime.fromString(
1395                                                            attributeValues[1])
1396        attributeQuery.id = attributeValues[2]
1397       
1398        for childElem in elem:
1399            localName = QName.getLocalPart(childElem.tag)
1400            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1401                # Parse Issuer
1402                attributeQuery.issuer = IssuerElementTree.fromXML(childElem)
1403               
1404            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1405                # Parse Subject
1406                attributeQuery.subject = SubjectElementTree.fromXML(childElem)
1407           
1408            elif localName == Attribute.DEFAULT_ELEMENT_LOCAL_NAME:
1409                attribute = AttributeElementTree.fromXML(childElem)
1410                attributeQuery.attributes.append(attribute)
1411            else:
1412                raise XMLTypeParseError("Unrecognised AttributeQuery child "
1413                                          "element \"%s\"" % localName)
1414       
1415        return attributeQuery
1416       
1417   
1418class ResponseElementTree(Response):
1419    """Represent a SAML Response in XML using ElementTree"""
1420       
1421    @classmethod
1422    def toXML(cls, response, **attributeValueElementTreeFactoryKw):
1423        """Create an XML representation of the input SAML Response
1424        object
1425
1426        @type response: saml.saml2.core.Response
1427        @param response: SAML Response
1428        @type attributeValueElementTreeFactoryKw: dict
1429        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1430        factory
1431        @rtype: ElementTree.Element
1432        @return: Response as ElementTree XML element
1433        """
1434        if not isinstance(response, Response):
1435            raise TypeError("Expecting %r class, got %r" % (Response, 
1436                                                            type(response)))
1437         
1438        if response.id is None:
1439            raise TypeError("SAML Response id is not set")
1440         
1441        if response.issueInstant is None:
1442            raise TypeError("SAML Response issueInstant is not set")
1443       
1444        # TODO: Does inResponseTo have to be set?  This implementation
1445        # currently enforces this ...
1446        if response.inResponseTo is None:
1447            raise TypeError("SAML Response inResponseTo identifier is not set")
1448       
1449        issueInstant = SAMLDateTime.toString(response.issueInstant)
1450        attrib = {
1451            cls.ID_ATTRIB_NAME: response.id,
1452            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1453            cls.IN_RESPONSE_TO_ATTRIB_NAME: response.inResponseTo,
1454           
1455            # Nb. Version is a SAMLVersion instance and requires explicit cast
1456            cls.VERSION_ATTRIB_NAME: str(response.version)
1457        }
1458       
1459        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))       
1460        elem = ElementTree.Element(tag, **attrib)
1461       
1462        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1463                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1464
1465        # Issuer may be omitted: saml-profiles-2.0-os Section 4.1.4.2
1466        if response.issuer is not None: 
1467            issuerElem = IssuerElementTree.toXML(response.issuer)
1468            elem.append(issuerElem)
1469
1470        statusElem = StatusElementTree.toXML(response.status)       
1471        elem.append(statusElem)
1472
1473        for assertion in response.assertions:
1474            # Factory enables support for multiple attribute types
1475            assertionElem = AssertionElementTree.toXML(assertion,
1476                                        **attributeValueElementTreeFactoryKw)
1477            elem.append(assertionElem)
1478       
1479        return elem
1480
1481    @classmethod
1482    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
1483        """Parse ElementTree element into a SAML Response object
1484       
1485        @type elem: ElementTree.Element
1486        @param elem: XML element containing the Response
1487        @type attributeValueElementTreeFactoryKw: dict
1488        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1489        @rtype: saml.saml2.core.Response
1490        @return: Response object
1491        """
1492        if not ElementTree.iselement(elem):
1493            raise TypeError("Expecting %r input type for parsing; got %r" %
1494                            (ElementTree.Element, elem))
1495
1496        if QName.getLocalPart(elem.tag) != Response.DEFAULT_ELEMENT_LOCAL_NAME:
1497            raise XMLTypeParseError("No \"%s\" element found" %
1498                                      Response.DEFAULT_ELEMENT_LOCAL_NAME)
1499       
1500        # Unpack attributes from top-level element
1501        attributeValues = []
1502        for attributeName in (Response.VERSION_ATTRIB_NAME,
1503                              Response.ISSUE_INSTANT_ATTRIB_NAME,
1504                              Response.ID_ATTRIB_NAME,
1505                              Response.IN_RESPONSE_TO_ATTRIB_NAME):
1506            attributeValue = elem.attrib.get(attributeName)
1507            if attributeValue is None:
1508                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1509                                          'element' %
1510                                         (attributeName,
1511                                          Response.DEFAULT_ELEMENT_LOCAL_NAME))
1512               
1513            attributeValues.append(attributeValue)
1514       
1515        response = Response()
1516        response.version = SAMLVersion(attributeValues[0])
1517        if response.version != SAMLVersion.VERSION_20:
1518            raise NotImplementedError("Parsing for %r is implemented for "
1519                                      "SAML version %s only; version %s is " 
1520                                      "not supported" % 
1521                                      (cls,
1522                                       SAMLVersion(SAMLVersion.VERSION_20),
1523                                       SAMLVersion(response.version)))
1524           
1525        response.issueInstant = SAMLDateTime.fromString(attributeValues[1])
1526        response.id = attributeValues[2]
1527        response.inResponseTo = attributeValues[3]
1528       
1529        for childElem in elem:
1530            localName = QName.getLocalPart(childElem.tag)
1531            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1532                # Parse Issuer
1533                response.issuer = IssuerElementTree.fromXML(childElem)
1534           
1535            elif localName == Status.DEFAULT_ELEMENT_LOCAL_NAME:
1536                # Get status of response
1537                response.status = StatusElementTree.fromXML(childElem)
1538               
1539            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1540                # Parse Subject
1541                response.subject = SubjectElementTree.fromXML(childElem)
1542           
1543            elif localName == Assertion.DEFAULT_ELEMENT_LOCAL_NAME:
1544                assertion = AssertionElementTree.fromXML(childElem,
1545                                        **attributeValueElementTreeFactoryKw)
1546                response.assertions.append(assertion)
1547            else:
1548                raise XMLTypeParseError('Unrecognised Response child '
1549                                          'element "%s"' % localName)
1550       
1551        return response
1552
1553
1554class ActionElementTree(Action):
1555    """Represent a SAML authorization action in XML using ElementTree"""
1556   
1557    @classmethod
1558    def toXML(cls, action):
1559        """Create an XML representation of the input SAML Name Identifier
1560        object
1561        @type action: saml.saml2.core.Action
1562        @param action: SAML subject
1563        @rtype: ElementTree.Element
1564        @return: Name ID as ElementTree XML element"""
1565       
1566        if not isinstance(action, Action):
1567            raise TypeError("Expecting %r class got %r" % (Action, 
1568                                                           type(action)))
1569           
1570        if not action.namespace:
1571            raise AttributeError("No action namespace set")
1572       
1573        attrib = {
1574            cls.NAMESPACE_ATTRIB_NAME: action.namespace
1575        }
1576        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1577        elem = ElementTree.Element(tag, **attrib)
1578       
1579        ElementTree._namespace_map[action.qname.namespaceURI
1580                                   ] = action.qname.prefix
1581       
1582        if not action.value:
1583            raise AttributeError("No action name set")
1584         
1585        elem.text = action.value
1586
1587        return elem
1588
1589    @classmethod
1590    def fromXML(cls, elem):
1591        """Parse ElementTree element into a SAML Action object
1592       
1593        @type elem: ElementTree.Element
1594        @param elem: Name ID as ElementTree XML element
1595        @rtype: saml.saml2.core.Action
1596        @return: SAML Name ID
1597        """
1598        if not ElementTree.iselement(elem):
1599            raise TypeError("Expecting %r input type for parsing; got %r" %
1600                            (ElementTree.Element, elem))
1601
1602        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1603            raise XMLTypeParseError("No \"%s\" element found" %
1604                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1605           
1606        namespace = elem.attrib.get(cls.NAMESPACE_ATTRIB_NAME)
1607        if namespace is None:
1608            raise XMLTypeParseError('No "%s" attribute found in "%s" '
1609                                    'element' %
1610                                    (cls.NAMESPACE_ATTRIB_NAME,
1611                                     cls.DEFAULT_ELEMENT_LOCAL_NAME))
1612        action = Action()
1613        action.namespace = namespace
1614        action.value = elem.text.strip() 
1615       
1616        return action
1617   
1618   
1619class AuthzDecisionQueryElementTree(AuthzDecisionQuery):
1620    """Represent a SAML Attribute Query in XML using ElementTree"""
1621       
1622    @classmethod
1623    def toXML(cls, authzDecisionQuery):
1624        """Create an XML representation of the input SAML Authorization
1625        Decision Query object
1626
1627        @type authzDecisionQuery: saml.saml2.core.AuthzDecisionQuery
1628        @param authzDecisionQuery: SAML Authorization Decision Query
1629        @rtype: ElementTree.Element
1630        @return: Attribute Query as ElementTree XML element
1631        """
1632        if not isinstance(authzDecisionQuery, AuthzDecisionQuery):
1633            raise TypeError("Expecting %r class got %r" % (AuthzDecisionQuery, 
1634                                                    type(authzDecisionQuery)))
1635           
1636        if not authzDecisionQuery.resource:
1637            raise AttributeError("No resource has been set for the "
1638                                 "AuthzDecisionQuery")
1639           
1640        issueInstant = SAMLDateTime.toString(authzDecisionQuery.issueInstant)
1641        attrib = {
1642            cls.ID_ATTRIB_NAME: authzDecisionQuery.id,
1643            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1644           
1645            # Nb. Version is a SAMLVersion instance and requires explicit cast
1646            cls.VERSION_ATTRIB_NAME: str(authzDecisionQuery.version),
1647           
1648            cls.RESOURCE_ATTRIB_NAME: authzDecisionQuery.resource
1649        }
1650       
1651        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1652        elem = ElementTree.Element(tag, **attrib)
1653       
1654        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1655                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1656       
1657        issuerElem = IssuerElementTree.toXML(authzDecisionQuery.issuer)
1658        elem.append(issuerElem)
1659
1660        subjectElem = SubjectElementTree.toXML(authzDecisionQuery.subject)
1661        elem.append(subjectElem)
1662
1663        for action in authzDecisionQuery.actions:
1664            # Factory enables support for multiple attribute types
1665            actionElem = ActionElementTree.toXML(action)
1666            elem.append(actionElem)
1667       
1668        if (authzDecisionQuery.evidence and 
1669            len(authzDecisionQuery.evidence.evidence) > 0):
1670            raise NotImplementedError("Conversion of AuthzDecisionQuery "
1671                                      "Evidence type to ElementTree Element is "
1672                                      "not currently supported")
1673           
1674        return elem
1675
1676    @classmethod
1677    def fromXML(cls, elem):
1678        """Parse ElementTree element into a SAML AuthzDecisionQuery object
1679       
1680        @type elem: ElementTree.Element
1681        @param elem: XML element containing the AuthzDecisionQuery
1682        @rtype: saml.saml2.core.AuthzDecisionQuery
1683        @return: AuthzDecisionQuery object
1684        """
1685        if not ElementTree.iselement(elem):
1686            raise TypeError("Expecting %r input type for parsing; got %r" %
1687                            (ElementTree.Element, elem))
1688
1689        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1690            raise XMLTypeParseError("No \"%s\" element found" %
1691                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1692       
1693        # Unpack attributes from top-level element
1694        attributeValues = []
1695        for attributeName in (cls.VERSION_ATTRIB_NAME,
1696                              cls.ISSUE_INSTANT_ATTRIB_NAME,
1697                              cls.ID_ATTRIB_NAME,
1698                              cls.RESOURCE_ATTRIB_NAME):
1699            attributeValue = elem.attrib.get(attributeName)
1700            if attributeValue is None:
1701                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1702                                 'element' %
1703                                 (attributeName,
1704                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
1705               
1706            attributeValues.append(attributeValue)
1707       
1708        authzDecisionQuery = AuthzDecisionQuery()
1709        authzDecisionQuery.version = SAMLVersion(attributeValues[0])
1710        if authzDecisionQuery.version != SAMLVersion.VERSION_20:
1711            raise NotImplementedError("Parsing for %r is implemented for "
1712                                      "SAML version %s only; version %s is " 
1713                                      "not supported" % 
1714                                      (cls,
1715                                       SAMLVersion(SAMLVersion.VERSION_20),
1716                                       SAMLVersion(authzDecisionQuery.version)))
1717           
1718        authzDecisionQuery.issueInstant = SAMLDateTime.fromString(
1719                                                            attributeValues[1])
1720        authzDecisionQuery.id = attributeValues[2]
1721       
1722        authzDecisionQuery.resource = attributeValues[3]
1723       
1724        for childElem in elem:
1725            localName = QName.getLocalPart(childElem.tag)
1726            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1727                # Parse Issuer
1728                authzDecisionQuery.issuer = IssuerElementTree.fromXML(childElem)
1729               
1730            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1731                # Parse Subject
1732                authzDecisionQuery.subject = SubjectElementTree.fromXML(childElem)
1733           
1734            elif localName == Action.DEFAULT_ELEMENT_LOCAL_NAME:
1735                action = ActionElementTree.fromXML(childElem)
1736                authzDecisionQuery.actions.append(action)
1737            else:
1738                raise XMLTypeParseError("Unrecognised AuthzDecisionQuery child "
1739                                        "element \"%s\"" % localName)
1740       
1741        return authzDecisionQuery
1742
Note: See TracBrowser for help on using the repository browser.