source: TI12-security/trunk/ndg_saml/ndg/saml/xml/etree.py @ 6900

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

Incomplete - task 6: Put NDG SAML package on PyPI

  • add documentation folder and Makefile for epydoc
  • updating epydoc for all modules and packages.
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__ = "http://www.apache.org/licenses/LICENSE-2.0"
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 ndg.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 ndg.saml.common import SAMLVersion
49from ndg.saml.common.xml import SAMLConstants
50from ndg.saml.common.xml import QName as GenericQName
51from ndg.saml.xml import XMLTypeParseError, UnknownAttrProfile
52from ndg.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        if elem.text is None:
1017            raise XMLTypeParseError('No SAML issuer value set')
1018       
1019        issuer.value = elem.text.strip() 
1020       
1021        return issuer
1022
1023       
1024class NameIdElementTree(NameID):
1025    """Represent a SAML Name Identifier in XML using ElementTree"""
1026   
1027    @classmethod
1028    def toXML(cls, nameID):
1029        """Create an XML representation of the input SAML Name Identifier
1030        object
1031        @type nameID: saml.saml2.core.NameID
1032        @param nameID: SAML name ID
1033        @rtype: ElementTree.Element
1034        @return: Name ID as ElementTree XML element"""
1035       
1036        if not isinstance(nameID, NameID):
1037            raise TypeError("Expecting %r class got %r" % (NameID, 
1038                                                           type(nameID)))
1039        attrib = {
1040            cls.FORMAT_ATTRIB_NAME: nameID.format
1041        }
1042        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1043        elem = ElementTree.Element(tag, **attrib)
1044       
1045        ElementTree._namespace_map[nameID.qname.namespaceURI
1046                                   ] = nameID.qname.prefix
1047       
1048        elem.text = nameID.value
1049
1050        return elem
1051
1052    @classmethod
1053    def fromXML(cls, elem):
1054        """Parse ElementTree element into a SAML NameID object
1055       
1056        @type elem: ElementTree.Element
1057        @param elem: Name ID as ElementTree XML element
1058        @rtype: saml.saml2.core.NameID
1059        @return: SAML Name ID
1060        """
1061        if not ElementTree.iselement(elem):
1062            raise TypeError("Expecting %r input type for parsing; got %r" %
1063                            (ElementTree.Element, elem))
1064
1065        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1066            raise XMLTypeParseError("No \"%s\" element found" %
1067                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1068           
1069        format = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
1070        if format is None:
1071            raise XMLTypeParseError('No "%s" attribute found in "%s" '
1072                                    'element' %
1073                                    (cls.FORMAT_ATTRIB_NAME,
1074                                     cls.DEFAULT_ELEMENT_LOCAL_NAME))
1075        nameID = NameID()
1076        nameID.format = format
1077        nameID.value = elem.text.strip() 
1078       
1079        return nameID
1080
1081
1082class SubjectElementTree(Subject):
1083    """Represent a SAML Subject in XML using ElementTree"""
1084   
1085    @classmethod
1086    def toXML(cls, subject):
1087        """Create an XML representation of the input SAML subject object
1088        @type subject: saml.saml2.core.Subject
1089        @param subject: SAML subject
1090        @rtype: ElementTree.Element
1091        @return: subject as ElementTree XML element
1092        """
1093        if not isinstance(subject, Subject):
1094            raise TypeError("Expecting %r class got %r" % (Subject, 
1095                                                           type(subject)))
1096           
1097        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
1098        elem = ElementTree.Element(tag)
1099       
1100        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1101                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1102
1103           
1104        nameIdElem = NameIdElementTree.toXML(subject.nameID)
1105        elem.append(nameIdElem)
1106       
1107        return elem
1108
1109    @classmethod
1110    def fromXML(cls, elem):
1111        """Parse ElementTree element into a SAML Subject object
1112       
1113        @type elem: ElementTree.Element
1114        @param elem: subject as ElementTree XML element
1115        @rtype: saml.saml2.core.Subject
1116        @return: SAML subject
1117        """
1118        if not ElementTree.iselement(elem):
1119            raise TypeError("Expecting %r input type for parsing; got %r" %
1120                            (ElementTree.Element, elem))
1121
1122        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1123            raise XMLTypeParseError("No \"%s\" element found" %
1124                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
1125           
1126        if len(elem) != 1:
1127            raise XMLTypeParseError("Expecting single Name ID child element "
1128                                      "for SAML Subject element")
1129           
1130        subject = Subject()
1131        subject.nameID = NameIdElementTree.fromXML(elem[0])
1132       
1133        return subject
1134
1135       
1136class StatusCodeElementTree(StatusCode):
1137    """Represent a SAML Status Code in XML using ElementTree"""
1138   
1139    @classmethod
1140    def toXML(cls, statusCode):
1141        """Create an XML representation of the input SAML Name Status Code
1142       
1143        @type statusCode: saml.saml2.core.StatusCode
1144        @param statusCode: SAML Status Code
1145        @rtype: ElementTree.Element
1146        @return: Status Code as ElementTree XML element"""
1147       
1148        if not isinstance(statusCode, StatusCode):
1149            raise TypeError("Expecting %r class got %r" % (StatusCode, 
1150                                                           type(statusCode)))
1151           
1152        attrib = {
1153            cls.VALUE_ATTRIB_NAME: statusCode.value
1154        }
1155        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1156        elem = ElementTree.Element(tag, **attrib)
1157       
1158        ElementTree._namespace_map[statusCode.qname.namespaceURI
1159                                   ] = statusCode.qname.prefix
1160
1161        return elem
1162
1163    @classmethod
1164    def fromXML(cls, elem):
1165        """Parse ElementTree element into a SAML StatusCode object
1166       
1167        @type elem: ElementTree.Element
1168        @param elem: Status Code as ElementTree XML element
1169        @rtype: saml.saml2.core.StatusCode
1170        @return: SAML Status Code
1171        """
1172        if not ElementTree.iselement(elem):
1173            raise TypeError("Expecting %r input type for parsing; got %r" %
1174                            (ElementTree.Element, elem))
1175
1176        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1177            raise XMLTypeParseError('No "%s" element found' %
1178                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1179           
1180        statusCode = StatusCode()
1181           
1182        value = elem.attrib.get(cls.VALUE_ATTRIB_NAME)
1183        if value is None:
1184            raise XMLTypeParseError('No "%s" attribute found in "%s" element' %
1185                                    (cls.VALUE_ATTRIB_NAME,
1186                                     cls.DEFAULT_ELEMENT_LOCAL_NAME))
1187        statusCode.value = value
1188       
1189        return statusCode
1190
1191       
1192class StatusMessageElementTree(StatusMessage):
1193    """Represent a SAML Status Message in XML using ElementTree"""
1194   
1195    @classmethod
1196    def toXML(cls, statusMessage):
1197        """Create an XML representation of the input SAML Name Status Message
1198       
1199        @type statusMessage: saml.saml2.core.StatusMessage
1200        @param statusMessage: SAML Status Message
1201        @rtype: ElementTree.Element
1202        @return: Status Code as ElementTree XML element"""
1203       
1204        if not isinstance(statusMessage, StatusMessage):
1205            raise TypeError("Expecting %r class got %r" % (StatusMessage, 
1206                                                           type(statusMessage)))
1207           
1208        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1209        elem = ElementTree.Element(tag)
1210       
1211        ElementTree._namespace_map[statusMessage.qname.namespaceURI
1212                                   ] = statusMessage.qname.prefix
1213       
1214        elem.text = statusMessage.value
1215
1216        return elem
1217
1218    @classmethod
1219    def fromXML(cls, elem):
1220        """Parse ElementTree element into a SAML StatusMessage object
1221       
1222        @type elem: ElementTree.Element
1223        @param elem: Status Code as ElementTree XML element
1224        @rtype: saml.saml2.core.StatusMessage
1225        @return: SAML Status Message
1226        """
1227        if not ElementTree.iselement(elem):
1228            raise TypeError("Expecting %r input type for parsing; got %r" %
1229                            (ElementTree.Element, elem))
1230
1231        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1232            raise XMLTypeParseError('No "%s" element found' %
1233                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1234           
1235        statusMessage = StatusMessage()
1236        if elem.text is not None:
1237            statusMessage.value = elem.text.strip() 
1238       
1239        return statusMessage
1240
1241
1242class StatusElementTree(Status):
1243    """Represent a SAML Status in XML using ElementTree"""
1244   
1245    @classmethod
1246    def toXML(cls, status):
1247        """Create an XML representation of the input SAML subject object
1248        @type subject: saml.saml2.core.Status
1249        @param subject: SAML subject
1250        @rtype: ElementTree.Element
1251        @return: subject as ElementTree XML element
1252        """
1253        if not isinstance(status, Status):
1254            raise TypeError("Expecting %r class got %r" % (status, 
1255                                                           type(Status)))
1256           
1257        tag = str(QName.fromGeneric(Status.DEFAULT_ELEMENT_NAME)) 
1258        elem = ElementTree.Element(tag)
1259       
1260        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1261                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1262       
1263        statusCodeElem = StatusCodeElementTree.toXML(status.statusCode)
1264        elem.append(statusCodeElem)
1265       
1266        # Status message is optional
1267        if status.statusMessage is not None and \
1268           status.statusMessage.value is not None:
1269            statusMessageElem = StatusMessageElementTree.toXML(
1270                                                        status.statusMessage)
1271            elem.append(statusMessageElem)
1272       
1273        if status.statusDetail is not None:
1274            raise NotImplementedError("StatusDetail XML serialisation is not "
1275                                      "implemented")
1276           
1277        return elem
1278
1279    @classmethod
1280    def fromXML(cls, elem):
1281        """Parse ElementTree element into a SAML Status object
1282       
1283        @type elem: ElementTree.Element
1284        @param elem: subject as ElementTree XML element
1285        @rtype: saml.saml2.core.Status
1286        @return: SAML subject
1287        """
1288        if not ElementTree.iselement(elem):
1289            raise TypeError("Expecting %r input type for parsing; got %r" %
1290                            (ElementTree.Element, elem))
1291
1292        if QName.getLocalPart(elem.tag) != Status.DEFAULT_ELEMENT_LOCAL_NAME:
1293            raise XMLTypeParseError('No "%s" element found' %
1294                                      Status.DEFAULT_ELEMENT_LOCAL_NAME)
1295           
1296        if len(elem) < 1:
1297            raise XMLTypeParseError("Expecting a StatusCode child element for "
1298                                    "SAML Status element")
1299           
1300        status = Status()
1301        for childElem in elem:
1302            localName = QName.getLocalPart(childElem.tag)
1303            if localName == StatusCode.DEFAULT_ELEMENT_LOCAL_NAME:
1304                status.statusCode = StatusCodeElementTree.fromXML(childElem)
1305               
1306            elif localName == StatusMessage.DEFAULT_ELEMENT_LOCAL_NAME:
1307                status.statusMessage = StatusMessageElementTree.fromXML(
1308                                                                childElem)
1309            elif localName == StatusDetail.DEFAULT_ELEMENT_LOCAL_NAME:
1310                raise NotImplementedError("XML parse of %s element is not "
1311                                    "implemented" %
1312                                    StatusDetail.DEFAULT_ELEMENT_LOCAL_NAME)
1313            else:
1314                raise XMLTypeParseError("Status child element name %r not "
1315                                        "recognised" % localName)
1316       
1317        return status
1318   
1319   
1320class AttributeQueryElementTree(AttributeQuery):
1321    """Represent a SAML Attribute Query in XML using ElementTree"""
1322       
1323    @classmethod
1324    def toXML(cls, attributeQuery, **attributeValueElementTreeFactoryKw):
1325        """Create an XML representation of the input SAML Attribute Query
1326        object
1327
1328        @type attributeQuery: saml.saml2.core.AttributeQuery
1329        @param attributeQuery: SAML Attribute Query
1330        @type attributeValueElementTreeFactoryKw: dict
1331        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1332        factory
1333        @rtype: ElementTree.Element
1334        @return: Attribute Query as ElementTree XML element
1335        """
1336        if not isinstance(attributeQuery, AttributeQuery):
1337            raise TypeError("Expecting %r class got %r" % (AttributeQuery, 
1338                                                        type(attributeQuery)))
1339           
1340       
1341        issueInstant = SAMLDateTime.toString(attributeQuery.issueInstant)
1342        attrib = {
1343            cls.ID_ATTRIB_NAME: attributeQuery.id,
1344            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1345           
1346            # Nb. Version is a SAMLVersion instance and requires explicit cast
1347            cls.VERSION_ATTRIB_NAME: str(attributeQuery.version)
1348        }
1349       
1350        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1351        elem = ElementTree.Element(tag, **attrib)
1352       
1353        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1354                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1355       
1356        issuerElem = IssuerElementTree.toXML(attributeQuery.issuer)
1357        elem.append(issuerElem)
1358
1359        subjectElem = SubjectElementTree.toXML(attributeQuery.subject)
1360        elem.append(subjectElem)
1361
1362        for attribute in attributeQuery.attributes:
1363            # Factory enables support for multiple attribute types
1364            attributeElem = AttributeElementTree.toXML(attribute,
1365                                        **attributeValueElementTreeFactoryKw)
1366            elem.append(attributeElem)
1367       
1368        return elem
1369
1370    @classmethod
1371    def fromXML(cls, elem):
1372        """Parse ElementTree element into a SAML AttributeQuery object
1373       
1374        @type elem: ElementTree.Element
1375        @param elem: XML element containing the AttributeQuery
1376        @rtype: saml.saml2.core.AttributeQuery
1377        @return: AttributeQuery object
1378        """
1379        if not ElementTree.iselement(elem):
1380            raise TypeError("Expecting %r input type for parsing; got %r" %
1381                            (ElementTree.Element, elem))
1382
1383        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1384            raise XMLTypeParseError("No \"%s\" element found" %
1385                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1386       
1387        # Unpack attributes from top-level element
1388        attributeValues = []
1389        for attributeName in (cls.VERSION_ATTRIB_NAME,
1390                              cls.ISSUE_INSTANT_ATTRIB_NAME,
1391                              cls.ID_ATTRIB_NAME):
1392            attributeValue = elem.attrib.get(attributeName)
1393            if attributeValue is None:
1394                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1395                                 'element' %
1396                                 (attributeName,
1397                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
1398               
1399            attributeValues.append(attributeValue)
1400       
1401        attributeQuery = AttributeQuery()
1402        attributeQuery.version = SAMLVersion(attributeValues[0])
1403        if attributeQuery.version != SAMLVersion.VERSION_20:
1404            raise NotImplementedError("Parsing for %r is implemented for "
1405                                      "SAML version %s only; version %s is " 
1406                                      "not supported" % 
1407                                      (cls,
1408                                       SAMLVersion(SAMLVersion.VERSION_20),
1409                                       SAMLVersion(attributeQuery.version)))
1410           
1411        attributeQuery.issueInstant = SAMLDateTime.fromString(
1412                                                            attributeValues[1])
1413        attributeQuery.id = attributeValues[2]
1414       
1415        for childElem in elem:
1416            localName = QName.getLocalPart(childElem.tag)
1417            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1418                # Parse Issuer
1419                attributeQuery.issuer = IssuerElementTree.fromXML(childElem)
1420               
1421            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1422                # Parse Subject
1423                attributeQuery.subject = SubjectElementTree.fromXML(childElem)
1424           
1425            elif localName == Attribute.DEFAULT_ELEMENT_LOCAL_NAME:
1426                attribute = AttributeElementTree.fromXML(childElem)
1427                attributeQuery.attributes.append(attribute)
1428            else:
1429                raise XMLTypeParseError("Unrecognised AttributeQuery child "
1430                                          "element \"%s\"" % localName)
1431       
1432        return attributeQuery
1433       
1434   
1435class ResponseElementTree(Response):
1436    """Represent a SAML Response in XML using ElementTree"""
1437       
1438    @classmethod
1439    def toXML(cls, response, **attributeValueElementTreeFactoryKw):
1440        """Create an XML representation of the input SAML Response
1441        object
1442
1443        @type response: saml.saml2.core.Response
1444        @param response: SAML Response
1445        @type attributeValueElementTreeFactoryKw: dict
1446        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1447        factory
1448        @rtype: ElementTree.Element
1449        @return: Response as ElementTree XML element
1450        """
1451        if not isinstance(response, Response):
1452            raise TypeError("Expecting %r class, got %r" % (Response, 
1453                                                            type(response)))
1454         
1455        if response.id is None:
1456            raise TypeError("SAML Response id is not set")
1457         
1458        if response.issueInstant is None:
1459            raise TypeError("SAML Response issueInstant is not set")
1460       
1461        # TODO: Does inResponseTo have to be set?  This implementation
1462        # currently enforces this ...
1463        if response.inResponseTo is None:
1464            raise TypeError("SAML Response inResponseTo identifier is not set")
1465       
1466        issueInstant = SAMLDateTime.toString(response.issueInstant)
1467        attrib = {
1468            cls.ID_ATTRIB_NAME: response.id,
1469            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1470            cls.IN_RESPONSE_TO_ATTRIB_NAME: response.inResponseTo,
1471           
1472            # Nb. Version is a SAMLVersion instance and requires explicit cast
1473            cls.VERSION_ATTRIB_NAME: str(response.version)
1474        }
1475       
1476        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))       
1477        elem = ElementTree.Element(tag, **attrib)
1478       
1479        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1480                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1481
1482        # Issuer may be omitted: saml-profiles-2.0-os Section 4.1.4.2
1483        if response.issuer is not None: 
1484            issuerElem = IssuerElementTree.toXML(response.issuer)
1485            elem.append(issuerElem)
1486
1487        statusElem = StatusElementTree.toXML(response.status)       
1488        elem.append(statusElem)
1489
1490        for assertion in response.assertions:
1491            # Factory enables support for multiple attribute types
1492            assertionElem = AssertionElementTree.toXML(assertion,
1493                                        **attributeValueElementTreeFactoryKw)
1494            elem.append(assertionElem)
1495       
1496        return elem
1497
1498    @classmethod
1499    def fromXML(cls, elem, **attributeValueElementTreeFactoryKw):
1500        """Parse ElementTree element into a SAML Response object
1501       
1502        @type elem: ElementTree.Element
1503        @param elem: XML element containing the Response
1504        @type attributeValueElementTreeFactoryKw: dict
1505        @param attributeValueElementTreeFactoryKw: keywords for AttributeValue
1506        @rtype: saml.saml2.core.Response
1507        @return: Response object
1508        """
1509        if not ElementTree.iselement(elem):
1510            raise TypeError("Expecting %r input type for parsing; got %r" %
1511                            (ElementTree.Element, elem))
1512
1513        if QName.getLocalPart(elem.tag) != Response.DEFAULT_ELEMENT_LOCAL_NAME:
1514            raise XMLTypeParseError("No \"%s\" element found" %
1515                                      Response.DEFAULT_ELEMENT_LOCAL_NAME)
1516       
1517        # Unpack attributes from top-level element
1518        attributeValues = []
1519        for attributeName in (Response.VERSION_ATTRIB_NAME,
1520                              Response.ISSUE_INSTANT_ATTRIB_NAME,
1521                              Response.ID_ATTRIB_NAME,
1522                              Response.IN_RESPONSE_TO_ATTRIB_NAME):
1523            attributeValue = elem.attrib.get(attributeName)
1524            if attributeValue is None:
1525                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1526                                          'element' %
1527                                         (attributeName,
1528                                          Response.DEFAULT_ELEMENT_LOCAL_NAME))
1529               
1530            attributeValues.append(attributeValue)
1531       
1532        response = Response()
1533        response.version = SAMLVersion(attributeValues[0])
1534        if response.version != SAMLVersion.VERSION_20:
1535            raise NotImplementedError("Parsing for %r is implemented for "
1536                                      "SAML version %s only; version %s is " 
1537                                      "not supported" % 
1538                                      (cls,
1539                                       SAMLVersion(SAMLVersion.VERSION_20),
1540                                       SAMLVersion(response.version)))
1541           
1542        response.issueInstant = SAMLDateTime.fromString(attributeValues[1])
1543        response.id = attributeValues[2]
1544        response.inResponseTo = attributeValues[3]
1545       
1546        for childElem in elem:
1547            localName = QName.getLocalPart(childElem.tag)
1548            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1549                # Parse Issuer
1550                response.issuer = IssuerElementTree.fromXML(childElem)
1551           
1552            elif localName == Status.DEFAULT_ELEMENT_LOCAL_NAME:
1553                # Get status of response
1554                response.status = StatusElementTree.fromXML(childElem)
1555               
1556            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1557                # Parse Subject
1558                response.subject = SubjectElementTree.fromXML(childElem)
1559           
1560            elif localName == Assertion.DEFAULT_ELEMENT_LOCAL_NAME:
1561                assertion = AssertionElementTree.fromXML(childElem,
1562                                        **attributeValueElementTreeFactoryKw)
1563                response.assertions.append(assertion)
1564            else:
1565                raise XMLTypeParseError('Unrecognised Response child '
1566                                          'element "%s"' % localName)
1567       
1568        return response
1569
1570
1571class ActionElementTree(Action):
1572    """Represent a SAML authorization action in XML using ElementTree"""
1573   
1574    @classmethod
1575    def toXML(cls, action):
1576        """Create an XML representation of the input SAML Name Identifier
1577        object
1578        @type action: saml.saml2.core.Action
1579        @param action: SAML subject
1580        @rtype: ElementTree.Element
1581        @return: Name ID as ElementTree XML element"""
1582       
1583        if not isinstance(action, Action):
1584            raise TypeError("Expecting %r class got %r" % (Action, 
1585                                                           type(action)))
1586           
1587        if not action.namespace:
1588            raise AttributeError("No action namespace set")
1589       
1590        attrib = {
1591            cls.NAMESPACE_ATTRIB_NAME: action.namespace
1592        }
1593        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1594        elem = ElementTree.Element(tag, **attrib)
1595       
1596        ElementTree._namespace_map[action.qname.namespaceURI
1597                                   ] = action.qname.prefix
1598       
1599        if not action.value:
1600            raise AttributeError("No action name set")
1601         
1602        elem.text = action.value
1603
1604        return elem
1605
1606    @classmethod
1607    def fromXML(cls, elem):
1608        """Parse ElementTree element into a SAML Action object
1609       
1610        @type elem: ElementTree.Element
1611        @param elem: Name ID as ElementTree XML element
1612        @rtype: saml.saml2.core.Action
1613        @return: SAML Name ID
1614        """
1615        if not ElementTree.iselement(elem):
1616            raise TypeError("Expecting %r input type for parsing; got %r" %
1617                            (ElementTree.Element, elem))
1618
1619        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1620            raise XMLTypeParseError("No \"%s\" element found" %
1621                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1622           
1623        action = Action()
1624        namespace = elem.attrib.get(cls.NAMESPACE_ATTRIB_NAME)
1625        if namespace is None:
1626            log.warning('No "%s" attribute found in "%s" element assuming '
1627                        '%r action namespace' %
1628                        (cls.NAMESPACE_ATTRIB_NAME,
1629                         cls.DEFAULT_ELEMENT_LOCAL_NAME,
1630                         action.namespace))
1631        else:
1632            action.namespace = namespace
1633           
1634        action.value = elem.text.strip() 
1635       
1636        return action
1637   
1638   
1639class AuthzDecisionQueryElementTree(AuthzDecisionQuery):
1640    """Represent a SAML Attribute Query in XML using ElementTree"""
1641       
1642    @classmethod
1643    def toXML(cls, authzDecisionQuery):
1644        """Create an XML representation of the input SAML Authorization
1645        Decision Query object
1646
1647        @type authzDecisionQuery: saml.saml2.core.AuthzDecisionQuery
1648        @param authzDecisionQuery: SAML Authorization Decision Query
1649        @rtype: ElementTree.Element
1650        @return: Attribute Query as ElementTree XML element
1651        """
1652        if not isinstance(authzDecisionQuery, AuthzDecisionQuery):
1653            raise TypeError("Expecting %r class got %r" % (AuthzDecisionQuery, 
1654                                                    type(authzDecisionQuery)))
1655           
1656        if not authzDecisionQuery.resource:
1657            raise AttributeError("No resource has been set for the "
1658                                 "AuthzDecisionQuery")
1659           
1660        issueInstant = SAMLDateTime.toString(authzDecisionQuery.issueInstant)
1661        attrib = {
1662            cls.ID_ATTRIB_NAME: authzDecisionQuery.id,
1663            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1664           
1665            # Nb. Version is a SAMLVersion instance and requires explicit cast
1666            cls.VERSION_ATTRIB_NAME: str(authzDecisionQuery.version),
1667           
1668            cls.RESOURCE_ATTRIB_NAME: authzDecisionQuery.resource
1669        }
1670       
1671        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
1672        elem = ElementTree.Element(tag, **attrib)
1673       
1674        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1675                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1676       
1677        issuerElem = IssuerElementTree.toXML(authzDecisionQuery.issuer)
1678        elem.append(issuerElem)
1679
1680        subjectElem = SubjectElementTree.toXML(authzDecisionQuery.subject)
1681        elem.append(subjectElem)
1682
1683        for action in authzDecisionQuery.actions:
1684            # Factory enables support for multiple attribute types
1685            actionElem = ActionElementTree.toXML(action)
1686            elem.append(actionElem)
1687       
1688        if (authzDecisionQuery.evidence and 
1689            len(authzDecisionQuery.evidence.evidence) > 0):
1690            raise NotImplementedError("Conversion of AuthzDecisionQuery "
1691                                      "Evidence type to ElementTree Element is "
1692                                      "not currently supported")
1693           
1694        return elem
1695
1696    @classmethod
1697    def fromXML(cls, elem):
1698        """Parse ElementTree element into a SAML AuthzDecisionQuery object
1699       
1700        @type elem: ElementTree.Element
1701        @param elem: XML element containing the AuthzDecisionQuery
1702        @rtype: saml.saml2.core.AuthzDecisionQuery
1703        @return: AuthzDecisionQuery object
1704        """
1705        if not ElementTree.iselement(elem):
1706            raise TypeError("Expecting %r input type for parsing; got %r" %
1707                            (ElementTree.Element, elem))
1708
1709        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
1710            raise XMLTypeParseError("No \"%s\" element found" %
1711                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
1712       
1713        # Unpack attributes from top-level element
1714        attributeValues = []
1715        for attributeName in (cls.VERSION_ATTRIB_NAME,
1716                              cls.ISSUE_INSTANT_ATTRIB_NAME,
1717                              cls.ID_ATTRIB_NAME,
1718                              cls.RESOURCE_ATTRIB_NAME):
1719            attributeValue = elem.attrib.get(attributeName)
1720            if attributeValue is None:
1721                raise XMLTypeParseError('No "%s" attribute found in "%s" '
1722                                 'element' %
1723                                 (attributeName,
1724                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
1725               
1726            attributeValues.append(attributeValue)
1727       
1728        authzDecisionQuery = AuthzDecisionQuery()
1729        authzDecisionQuery.version = SAMLVersion(attributeValues[0])
1730        if authzDecisionQuery.version != SAMLVersion.VERSION_20:
1731            raise NotImplementedError("Parsing for %r is implemented for "
1732                                      "SAML version %s only; version %s is " 
1733                                      "not supported" % 
1734                                      (cls,
1735                                       SAMLVersion(SAMLVersion.VERSION_20),
1736                                       SAMLVersion(authzDecisionQuery.version)))
1737           
1738        authzDecisionQuery.issueInstant = SAMLDateTime.fromString(
1739                                                            attributeValues[1])
1740        authzDecisionQuery.id = attributeValues[2]       
1741        authzDecisionQuery.resource = attributeValues[3]
1742       
1743        for childElem in elem:
1744            localName = QName.getLocalPart(childElem.tag)
1745            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1746                # Parse Issuer
1747                authzDecisionQuery.issuer = IssuerElementTree.fromXML(childElem)
1748               
1749            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1750                # Parse Subject
1751                authzDecisionQuery.subject = SubjectElementTree.fromXML(childElem)
1752           
1753            elif localName == Action.DEFAULT_ELEMENT_LOCAL_NAME:
1754                action = ActionElementTree.fromXML(childElem)
1755                authzDecisionQuery.actions.append(action)
1756            else:
1757                raise XMLTypeParseError("Unrecognised AuthzDecisionQuery child "
1758                                        "element \"%s\"" % localName)
1759       
1760        return authzDecisionQuery
1761
Note: See TracBrowser for help on using the repository browser.