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

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

Unit testing AuthzDecisionStatement? added to a Response.

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