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

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