source: TI12-security/trunk/python/ndg.security.saml/saml/xml/etree.py @ 5597

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

Refactored SAML package structure into saml2, core and common sub-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__ = "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 import XSStringAttributeValue, XSGroupRoleAttributeValue
41
42from saml.saml2.core import SAMLObject, Attribute, AttributeStatement, \
43    Assertion, Conditions, AttributeValue, AttributeQuery, Subject, NameID, \
44    Issuer, SAMLVersion, Response, Status, StatusCode
45from saml.common.xml import SAMLConstants
46from saml.xml import IssueInstantXMLObject, XMLObjectParseError
47from saml.xml import QName as GenericQName
48
49# Generic Helper classes
50class QName(ElementTree.QName):
51    """Extend ElementTree implementation for improved attribute access support
52    """ 
53
54    # ElementTree tag is of the form {namespace}localPart.  getNs extracts the
55    # namespace from within the brackets but if not found returns ''
56    getNs = staticmethod(lambda tag: getattr(re.search('(?<=\{).+(?=\})', tag),
57                                             'group', 
58                                             str)())
59                                             
60    getLocalPart = staticmethod(lambda tag: tag.rsplit('}',1)[-1])
61
62    def __init__(self, namespaceURI, tag=None, prefix=None):
63        ElementTree.QName.__init__(self, namespaceURI, tag=tag)
64       
65        if tag:
66            self.namespaceURI = namespaceURI
67            self.localPart = tag
68        else:
69            self.namespaceURI = QName.getNs(namespaceURI)
70            self.localPart = QName.getLocalPart(namespaceURI)
71           
72        self.prefix = prefix
73   
74    def _getPrefix(self):
75        return self.__prefix
76
77    def _setPrefix(self, value):
78        self.__prefix = value
79   
80    prefix = property(_getPrefix, _setPrefix, None, "Prefix")
81
82    def _getLocalPart(self):
83        return self.__localPart
84   
85    def _setLocalPart(self, value):
86        self.__localPart = value
87       
88    localPart = property(_getLocalPart, _setLocalPart, None, "LocalPart")
89
90    def _getNamespaceURI(self):
91        return self.__namespaceURI
92
93    def _setNamespaceURI(self, value):
94        self.__namespaceURI = value
95 
96    namespaceURI = property(_getNamespaceURI, _setNamespaceURI, None, 
97                            "Namespace URI'")
98
99    @classmethod
100    def fromGeneric(cls, genericQName):
101        '''Cast the generic QName type in saml.xml to the ElementTree specific
102        implementation'''
103        if not isinstance(genericQName, GenericQName):
104            raise TypeError("Expecting %r for QName, got %r" % (GenericQName,
105                                                        type(genericQName)))
106           
107        qname = cls(genericQName.namespaceURI, 
108                    tag=genericQName.localPart,
109                    prefix=genericQName.prefix)
110        return qname
111   
112   
113def prettyPrint(*arg, **kw):
114    '''Lightweight pretty printing of ElementTree elements'''
115   
116    # Keep track of namespace declarations made so they're not repeated
117    declaredNss = []
118   
119    _prettyPrint = PrettyPrint(declaredNss)
120    return _prettyPrint(*arg, **kw)
121
122
123class PrettyPrint(object):
124    def __init__(self, declaredNss):
125        self.declaredNss = declaredNss
126   
127    @staticmethod
128    def estrip(elem):
129        ''' Just want to get rid of unwanted whitespace '''
130        if elem is None:
131            return ''
132        else:
133            # just in case the elem is another simple type - e.g. int -
134            # wrapper it as a string
135            return str(elem).strip()
136       
137    def __call__(self, elem, indent='', html=0, space=' '*4):
138        '''Most of the work done in this wrapped function - wrapped so that
139        state can be maintained for declared namespace declarations during
140        recursive calls using "declaredNss" above''' 
141        strAttribs = []
142        for attr, attrVal in elem.attrib.items():
143            nsDeclaration = ''
144           
145            attrNamespace = QName.getNs(attr)
146            if attrNamespace:
147                nsPrefix = ElementTree._namespace_map.get(attrNamespace)
148                if nsPrefix is None:
149                    raise KeyError('prettyPrint: missing namespace "%s" for ' 
150                                   'ElementTree._namespace_map'%attrNamespace)
151               
152                attr = "%s:%s" % (nsPrefix, QName.getLocalPart(attr))
153               
154                if attrNamespace not in self.declaredNss:
155                    nsDeclaration = ' xmlns:%s="%s"' % (nsPrefix,attrNamespace)
156                    self.declaredNss.append(attrNamespace)
157               
158            strAttribs.append('%s %s="%s"' % (nsDeclaration, attr, attrVal))
159           
160        strAttrib = ''.join(strAttribs)
161       
162        namespace = QName.getNs(elem.tag)
163        nsPrefix = ElementTree._namespace_map.get(namespace)
164        if nsPrefix is None:
165            raise KeyError('prettyPrint: missing namespace "%s" for ' 
166                           'ElementTree._namespace_map' % namespace)
167           
168        tag = "%s:%s" % (nsPrefix, QName.getLocalPart(elem.tag))
169       
170        # Put in namespace declaration if one doesn't already exist
171        # FIXME: namespace declaration handling is wrong for handling child
172        # element scope
173        if namespace in self.declaredNss:
174            nsDeclaration = ''
175        else:
176            nsDeclaration = ' xmlns:%s="%s"' % (nsPrefix, namespace)
177            self.declaredNss.append(namespace)
178           
179        result = '%s<%s%s%s>%s' % (indent, tag, nsDeclaration, strAttrib, 
180                                   PrettyPrint.estrip(elem.text))
181       
182        children = len(elem)
183        if children:
184            for child in elem:
185                declaredNss = self.declaredNss[:]
186                _prettyPrint = PrettyPrint(declaredNss)
187                result += '\n'+ _prettyPrint(child, indent=indent+space) 
188               
189            result += '\n%s%s</%s>' % (indent,
190                                       PrettyPrint.estrip(child.tail),
191                                       tag)
192        else:
193            result += '</%s>' % tag
194           
195        return result
196
197
198# ElementTree SAML wrapper classes
199class ConditionsElementTree(Conditions, IssueInstantXMLObject):
200    """ElementTree based XML representation of Conditions class
201    """
202   
203    @classmethod
204    def create(cls, conditions):
205        """Make a tree of a XML elements based on the assertion conditions"""
206       
207        if not isinstance(conditions, Conditions):
208            raise TypeError("Expecting %r type got: %r" % (Conditions,
209                                                           conditions))
210       
211        notBeforeStr = cls.datetime2Str(conditions.notBefore)
212        notOnOrAfterStr = cls.datetime2Str(conditions.notOnOrAfter)
213        attrib = {
214            cls.NOT_BEFORE_ATTRIB_NAME: notBeforeStr,
215            cls.NOT_ON_OR_AFTER_ATTRIB_NAME: notOnOrAfterStr,
216        }
217       
218        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
219        elem = ElementTree.Element(tag, **attrib)
220       
221        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
222                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
223
224        for condition in conditions.conditions:
225            raise NotImplementedError("Conditions list creation is not "
226                                      "implemented")
227               
228        return elem
229               
230class AssertionElementTree(Assertion, IssueInstantXMLObject):
231    """ElementTree based XML representation of Assertion class
232    """
233   
234    @classmethod
235    def create(cls, 
236               assertion, 
237               **attributeValueElementTreeFactoryKw):
238        """Make a tree of a XML elements based on the assertion"""
239       
240        if not isinstance(assertion, Assertion):
241            raise TypeError("Expecting %r type got: %r"%(Assertion, assertion))
242       
243        issueInstant = cls.datetime2Str(assertion.issueInstant)
244        attrib = {
245            cls.ID_ATTRIB_NAME: assertion.id,
246            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
247           
248            # Nb. Version is a SAMLVersion instance and requires explicit cast
249            cls.VERSION_ATTRIB_NAME: str(assertion.version)
250        }
251        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
252        elem = ElementTree.Element(tag, **attrib)
253       
254        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
255                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
256       
257        if assertion.issuer is not None:
258            issuerElem = IssuerElementTree.create(assertion.issuer)
259            elem.append(issuerElem)
260       
261        if assertion.subject is not None:
262            subjectElem = SubjectElementTree.create(assertion.subject)
263            elem.append(subjectElem)
264
265        if assertion.advice:
266            raise NotImplementedError("Assertion Advice creation is not "
267                                      "implemented")
268
269        if assertion.conditions is not None:
270            conditionsElem = ConditionsElementTree.create(assertion.conditions)
271            elem.append(conditionsElem)
272           
273        for statement in assertion.statements:
274            raise NotImplementedError("Assertion Statement creation is not "
275                                      "implemented")
276       
277        for authnStatement in assertion.authnStatements:
278            raise NotImplementedError("Assertion Authentication Statement "
279                                      "creation is not implemented")
280       
281        for authzDecisionStatement in assertion.authzDecisionStatements:
282            raise NotImplementedError("Assertion Authorisation Decision "
283                                      "Statement creation is not implemented")
284           
285        for attributeStatement in assertion.attributeStatements:
286            attributeStatementElem = AttributeStatementElementTree.create(
287                                        attributeStatement,
288                                        **attributeValueElementTreeFactoryKw)
289            elem.append(attributeStatementElem)
290       
291        return elem
292
293 
294class AttributeStatementElementTree(AttributeStatement):
295    """ElementTree XML representation of AttributeStatement"""
296   
297    @classmethod
298    def create(cls, 
299               attributeStatement, 
300               **attributeValueElementTreeFactoryKw):
301        if not isinstance(attributeStatement, AttributeStatement):
302            raise TypeError("Expecting %r type got: %r" % (AttributeStatement, 
303                                                           attributeStatement))
304           
305        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
306        elem = ElementTree.Element(tag)
307        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
308                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
309
310        for attribute in attributeStatement.attributes:
311            # Factory enables support for multiple attribute types
312            attributeElem = AttributeElementTree.create(attribute,
313                                        **attributeValueElementTreeFactoryKw)
314            elem.append(attributeElem)
315       
316        return elem
317   
318
319class AttributeElementTree(Attribute):
320    """ElementTree XML representation of SAML Attribute object.  Extend
321    to make Attribute types""" 
322
323    @classmethod
324    def create(cls, 
325               attribute, 
326               **attributeValueElementTreeFactoryKw):
327        """Make 'Attribute' element"""
328       
329        if not isinstance(attribute, Attribute):
330            raise TypeError("Expecting %r type got: %r"%(Attribute, attribute))
331       
332        tag = str(QName.fromGeneric(Attribute.DEFAULT_ELEMENT_NAME))   
333        elem = ElementTree.Element(tag)
334        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
335                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
336       
337        if attribute.friendlyName:
338            elem.set(cls.FRIENDLY_NAME_ATTRIB_NAME, attribute.friendlyName) 
339             
340        if attribute.name:
341            elem.set(cls.NAME_ATTRIB_NAME, attribute.name)
342       
343        if attribute.nameFormat:
344            elem.set(cls.NAME_FORMAT_ATTRIB_NAME, attribute.nameFormat)
345
346        for attributeValue in attribute.attributeValues:
347            factory = AttributeValueElementTreeFactory(
348                                        **attributeValueElementTreeFactoryKw)
349           
350            attributeValueElementTree = factory(attributeValue)
351           
352            attributeValueElem=attributeValueElementTree.create(attributeValue)
353            elem.append(attributeValueElem)
354           
355        return elem
356 
357    @classmethod
358    def parse(cls, elem, **attributeValueElementTreeFactoryKw):
359        """Parse ElementTree element into a SAML Attribute object
360       
361        @type elem: ElementTree.Element
362        @param elem: Attribute as ElementTree XML element
363        @rtype: ndg.security.common.saml.Attribute
364        @return: SAML Attribute
365        """
366        if not ElementTree.iselement(elem):
367            raise TypeError("Expecting %r input type for parsing; got %r" %
368                            (ElementTree.Element, elem))
369
370        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
371            raise XMLObjectParseError("No \"%s\" element found" %
372                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
373           
374        attribute = Attribute()
375           
376        name = elem.attrib.get(cls.NAME_ATTRIB_NAME)
377        if name is not None:
378            attribute.name = name
379           
380        friendlyName = elem.attrib.get(cls.FRIENDLY_NAME_ATTRIB_NAME)
381        if friendlyName is not None:
382            attribute.friendlyName = friendlyName
383           
384        nameFormat = elem.attrib.get(cls.NAME_FORMAT_ATTRIB_NAME)   
385        if nameFormat is not None:
386            attribute.nameFormat = nameFormat
387
388        for childElem in elem:
389            localName = QName.getLocalPart(childElem.tag)
390            if localName != AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME:
391                raise XMLObjectParseError('Expecting "%s" element; found "%s"'%
392                                    (AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME,
393                                     localName))
394           
395            # Find XML type attribute to key which AttributeValue sub type to
396            # instantiate
397            attributeValueTypeId = None
398            for attribName, attribVal in childElem.attrib.items():
399                qname = QName(attribName)
400                if qname.localPart == "type":
401                    attributeValueTypeId = attribVal
402                    break
403               
404            if attributeValueTypeId is None:
405                raise XMLObjectParseError("Unable to determine type for "
406                                          "AttributeValue")
407               
408            factory = AttributeValueElementTreeFactory(
409                                        **attributeValueElementTreeFactoryKw)
410   
411            attributeValueElementTreeClass = factory(attributeValueTypeId)
412            attributeValue = attributeValueElementTreeClass.parse(childElem)
413            attribute.attributeValues.append(attributeValue)
414       
415        return attribute
416       
417   
418class AttributeValueElementTreeBase(AttributeValue):
419    """Base class ElementTree XML representation of SAML Attribute Value""" 
420   
421    @classmethod
422    def create(cls, attributeValue):
423        """Make 'Attribute' XML element"""
424
425        if not isinstance(attributeValue, AttributeValue):
426            raise TypeError("Expecting %r type got: %r" % (AttributeValue, 
427                                                           attributeValue))
428           
429        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))   
430        elem = ElementTree.Element(tag)
431        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
432                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
433
434        return elem
435
436
437class XSStringAttributeValueElementTree(AttributeValueElementTreeBase,
438                                        XSStringAttributeValue):
439    """ElementTree XML representation of SAML String type Attribute Value""" 
440   
441    @classmethod
442    def create(cls, attributeValue):
443        """Create an XML representation of the input SAML Attribute Value"""
444        elem = AttributeValueElementTreeBase.create(attributeValue)
445       
446        if not isinstance(attributeValue, XSStringAttributeValue):
447            raise TypeError("Expecting %r type got: %r" % 
448                            (XSStringAttributeValue, attributeValue)) 
449       
450        # Have to explicitly add namespace declaration here rather use
451        # ElementTree._namespace_map because the prefixes are used for
452        # attributes not element names       
453        elem.set("%s:%s" % (SAMLConstants.XMLNS_PREFIX, 
454                            SAMLConstants.XSD_PREFIX),
455                 SAMLConstants.XSD_NS)
456                                   
457        elem.set("%s:%s" % (SAMLConstants.XMLNS_PREFIX, 
458                            SAMLConstants.XSI_PREFIX),
459                 SAMLConstants.XSI_NS)
460       
461        elem.set("%s:%s" % (SAMLConstants.XSI_PREFIX, 'type'), 
462                 "%s:%s" % (SAMLConstants.XSD_PREFIX, 
463                            cls.TYPE_LOCAL_NAME))
464
465        elem.text = attributeValue.value
466
467        return elem
468
469    @classmethod
470    def parse(cls, elem):
471        """Parse ElementTree xs:string element into a SAML
472        XSStringAttributeValue object
473       
474        @type elem: ElementTree.Element
475        @param elem: Attribute value as ElementTree XML element
476        @rtype: ndg.security.common.saml.AttributeValue
477        @return: SAML Attribute value
478        """
479        if not ElementTree.iselement(elem):
480            raise TypeError("Expecting %r input type for parsing; got %r" %
481                            (ElementTree.Element, elem))
482
483        localName = QName.getLocalPart(elem.tag)
484        if localName != cls.DEFAULT_ELEMENT_LOCAL_NAME:
485            raise XMLObjectParseError("No \"%s\" element found" %
486                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
487       
488        # Parse the attribute type checking that it is set to the expected
489        # string type
490        typeQName = QName(SAMLConstants.XSI_NS, tag='type')
491       
492        typeValue = elem.attrib.get(str(typeQName), '')
493        typeValueLocalName = typeValue.split(':')[-1]
494        if typeValueLocalName != cls.TYPE_LOCAL_NAME:
495            raise XMLObjectParseError('Expecting "%s" type; got "%s"' %
496                                      (cls.TYPE_LOCAL_NAME,
497                                       typeValueLocalName))
498       
499        # Update namespace map as an XSI type has been referenced.  This will
500        # ensure the correct prefix is applied if it is re-serialised.
501        ElementTree._namespace_map[SAMLConstants.XSI_NS
502                                   ] = SAMLConstants.XSI_PREFIX
503                                     
504        attributeValue = XSStringAttributeValue()
505        if elem.text is not None:
506            attributeValue.value = elem.text.strip()
507
508        return attributeValue
509
510
511class XSGroupRoleAttributeValueElementTree(AttributeValueElementTreeBase,
512                                           XSGroupRoleAttributeValue):
513    """ElementTree XML representation of Earth System Grid custom Group/Role
514    Attribute Value""" 
515
516    @classmethod
517    def create(cls, attributeValue):
518        """Create an XML representation of the input SAML Attribute Value"""
519        elem = AttributeValueElementTreeBase.create(attributeValue)
520       
521        if not isinstance(attributeValue, XSGroupRoleAttributeValue):
522            raise TypeError("Expecting %r type; got: %r" % 
523                            (XSGroupRoleAttributeValue, type(attributeValue)))
524           
525        ElementTree._namespace_map[attributeValue.namespaceURI
526                                   ] = attributeValue.namespacePrefix
527       
528        elem.set(cls.GROUP_ATTRIB_NAME, attributeValue.group)
529        elem.set(cls.ROLE_ATTRIB_NAME, attributeValue.role)
530
531        return elem
532
533
534class AttributeValueElementTreeFactory(object):
535    """Class factory for AttributeValue ElementTree classes.  These classes are
536    used to represent SAML Attribute value types
537   
538    @type classMap: dict
539    @cvar classMap: mapping between SAML AttributeValue class and its
540    ElementTree handler class
541    @type idMap: dict
542    @cvar idMap: mapping between SAML AttributeValue string identifier and
543    its ElementTree handler class
544    """
545    classMap = {
546        XSStringAttributeValue: XSStringAttributeValueElementTree
547    }
548   
549    idMap = {
550        "xs:string": XSStringAttributeValueElementTree
551    }
552   
553    def __init__(self, customClassMap={}, customIdMap={}): 
554        """Set-up a SAML class to ElementTree mapping
555        @type customClassMap: dict
556        @param customClassMap: mapping for custom SAML AttributeValue classes
557        to their respective ElementTree based representations.  This appends
558        to self.__classMap
559        @type customIdMap: dict
560        @param customIdMap: string ID based mapping for custom SAML
561        AttributeValue classes to their respective ElementTree based
562        representations.  As with customClassMap, this appends to
563        to the respective self.__idMap
564        """
565        self.__classMap = AttributeValueElementTreeFactory.classMap
566        for samlClass, etreeClass in customClassMap.items(): 
567            if not issubclass(samlClass, AttributeValue):
568                raise TypeError("Input custom class must be derived from %r, "
569                                "got %r instead" % (Attribute, samlClass))
570               
571            self.__classMap[samlClass] = etreeClass
572
573        self.__idMap = AttributeValueElementTreeFactory.idMap
574        for samlId, etreeClass in customIdMap.items(): 
575            if not isinstance(samlId, basestring):
576                raise TypeError("Input custom SAML identifier must be a "
577                                "string, got %r instead" % samlId)
578               
579            self.__idMap[samlId] = etreeClass
580           
581    def __call__(self, input):
582        """Create an ElementTree object based on the Attribute class type
583        passed in
584       
585        @type input: ndg.security.common.saml.AttributeValue or basestring
586        @param input: pass an AttributeValue derived type or a string.  If
587        an AttributeValue type, then self.__classMap is checked for a matching
588        AttributeValue class entry, if a string is passed, self.__idMap is
589        checked for a matching string ID.  In both cases, if a match is
590        found an ElementTree class is returned which can render or parse
591        the relevant AttributeValue class
592        """
593        if isinstance(input, AttributeValue):
594            xmlObjectClass = self.__classMap.get(input.__class__)
595            if xmlObjectClass is None:
596                raise TypeError("no matching XMLObject class representation "
597                                "for SAML class %r" % input.__class__)
598               
599        elif isinstance(input, basestring):
600            xmlObjectClass = self.__idMap.get(input)
601            if xmlObjectClass is None:
602                raise TypeError("no matching XMLObject class representation "
603                                "for SAML AttributeValue type %s" % input)
604        else:
605            raise TypeError("Expecting %r class got %r" % (AttributeValue, 
606                                                           type(input)))
607           
608           
609        return xmlObjectClass
610
611       
612class IssuerElementTree(Issuer):
613    """Represent a SAML Issuer element in XML using ElementTree"""
614   
615    @classmethod
616    def create(cls, issuer):
617        """Create an XML representation of the input SAML issuer object"""
618        if not isinstance(issuer, Issuer):
619            raise TypeError("Expecting %r class got %r" % (Issuer, 
620                                                           type(issuer)))
621        attrib = {
622            cls.FORMAT_ATTRIB_NAME: issuer.format
623        }
624        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
625        elem = ElementTree.Element(tag, **attrib)
626        ElementTree._namespace_map[issuer.qname.namespaceURI
627                                   ] = issuer.qname.prefix
628                                   
629        elem.text = issuer.value
630
631        return elem
632
633    @classmethod
634    def parse(cls, elem):
635        """Parse ElementTree element into a SAML Issuer instance"""
636        if not ElementTree.iselement(elem):
637            raise TypeError("Expecting %r input type for parsing; got %r" %
638                            (ElementTree.Element, elem))
639
640        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
641            raise XMLObjectParseError('No "%s" element found' %
642                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
643           
644        issuerFormat = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
645        if issuerFormat is None:
646            raise XMLObjectParseError('No "%s" attribute found in "%s" '
647                                      'element' %
648                                      (issuerFormat,
649                                       cls.DEFAULT_ELEMENT_LOCAL_NAME))
650        issuer = Issuer()
651        issuer.format = issuerFormat
652        issuer.value = elem.text.strip() 
653       
654        return issuer
655
656       
657class NameIdElementTree(NameID):
658    """Represent a SAML Name Identifier in XML using ElementTree"""
659   
660    @classmethod
661    def create(cls, nameID):
662        """Create an XML representation of the input SAML Name Identifier
663        object
664        @type nameID: ndg.security.common.saml.Subject
665        @param nameID: SAML subject
666        @rtype: ElementTree.Element
667        @return: Name ID as ElementTree XML element"""
668       
669        if not isinstance(nameID, NameID):
670            raise TypeError("Expecting %r class got %r" % (NameID, 
671                                                           type(nameID)))
672        attrib = {
673            cls.FORMAT_ATTRIB_NAME: nameID.format
674        }
675        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
676        elem = ElementTree.Element(tag, **attrib)
677       
678        ElementTree._namespace_map[nameID.qname.namespaceURI
679                                   ] = nameID.qname.prefix
680       
681        elem.text = nameID.value
682
683        return elem
684
685    @classmethod
686    def parse(cls, elem):
687        """Parse ElementTree element into a SAML NameID object
688       
689        @type elem: ElementTree.Element
690        @param elem: Name ID as ElementTree XML element
691        @rtype: ndg.security.common.saml.NameID
692        @return: SAML Name ID
693        """
694        if not ElementTree.iselement(elem):
695            raise TypeError("Expecting %r input type for parsing; got %r" %
696                            (ElementTree.Element, elem))
697
698        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
699            raise XMLObjectParseError("No \"%s\" element found" %
700                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
701           
702        format = elem.attrib.get(cls.FORMAT_ATTRIB_NAME)
703        if format is None:
704            raise XMLObjectParseError('No "%s" attribute found in "%s" '
705                                      'element' %
706                                      (format,
707                                       cls.DEFAULT_ELEMENT_LOCAL_NAME))
708        nameID = NameID()
709        nameID.format = format
710        nameID.value = elem.text.strip() 
711       
712        return nameID
713
714
715class SubjectElementTree(Subject):
716    """Represent a SAML Subject in XML using ElementTree"""
717   
718    @classmethod
719    def create(cls, subject):
720        """Create an XML representation of the input SAML subject object
721        @type subject: ndg.security.common.saml.Subject
722        @param subject: SAML subject
723        @rtype: ElementTree.Element
724        @return: subject as ElementTree XML element
725        """
726        if not isinstance(subject, Subject):
727            raise TypeError("Expecting %r class got %r" % (Subject, 
728                                                           type(subject)))
729           
730        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME)) 
731        elem = ElementTree.Element(tag)
732       
733        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
734                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
735
736           
737        nameIdElem = NameIdElementTree.create(subject.nameID)
738        elem.append(nameIdElem)
739       
740        return elem
741
742    @classmethod
743    def parse(cls, elem):
744        """Parse ElementTree element into a SAML Subject object
745       
746        @type elem: ElementTree.Element
747        @param elem: subject as ElementTree XML element
748        @rtype: ndg.security.common.saml.Subject
749        @return: SAML subject
750        """
751        if not ElementTree.iselement(elem):
752            raise TypeError("Expecting %r input type for parsing; got %r" %
753                            (ElementTree.Element, elem))
754
755        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
756            raise XMLObjectParseError("No \"%s\" element found" %
757                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
758           
759        if len(elem) != 1:
760            raise XMLObjectParseError("Expecting single Name ID child element "
761                                      "for SAML Subject element")
762           
763        subject = Subject()
764        subject.nameID = NameIdElementTree.parse(elem[0])
765       
766        return subject
767
768       
769class StatusCodeElementTree(StatusCode):
770    """Represent a SAML Name Identifier in XML using ElementTree"""
771   
772    @classmethod
773    def create(cls, statusCode):
774        """Create an XML representation of the input SAML Name Status Code
775       
776        @type statusCode: ndg.security.common.saml.StatusCode
777        @param statusCode: SAML Status Code
778        @rtype: ElementTree.Element
779        @return: Status Code as ElementTree XML element"""
780       
781        if not isinstance(statusCode, StatusCode):
782            raise TypeError("Expecting %r class got %r" % (StatusCode, 
783                                                           type(statusCode)))
784           
785        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
786        elem = ElementTree.Element(tag)
787       
788        ElementTree._namespace_map[statusCode.qname.namespaceURI
789                                   ] = statusCode.qname.prefix
790       
791        elem.text = statusCode.value
792
793        return elem
794
795    @classmethod
796    def parse(cls, elem):
797        """Parse ElementTree element into a SAML StatusCode object
798       
799        @type elem: ElementTree.Element
800        @param elem: Status Code as ElementTree XML element
801        @rtype: ndg.security.common.saml.StatusCode
802        @return: SAML Status Code
803        """
804        if not ElementTree.iselement(elem):
805            raise TypeError("Expecting %r input type for parsing; got %r" %
806                            (ElementTree.Element, elem))
807
808        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
809            raise XMLObjectParseError('No "%s" element found' %
810                                      cls.DEFAULT_ELEMENT_LOCAL_NAME)
811           
812        statusCode = StatusCode()
813        statusCode.value = elem.text.strip() 
814       
815        return statusCode
816
817
818class StatusElementTree(Status):
819    """Represent a SAML Status in XML using ElementTree"""
820   
821    @classmethod
822    def create(cls, status):
823        """Create an XML representation of the input SAML subject object
824        @type subject: ndg.security.common.saml.Status
825        @param subject: SAML subject
826        @rtype: ElementTree.Element
827        @return: subject as ElementTree XML element
828        """
829        if not isinstance(status, Status):
830            raise TypeError("Expecting %r class got %r" % (status, 
831                                                           type(Status)))
832           
833        tag = str(QName.fromGeneric(Status.DEFAULT_ELEMENT_NAME)) 
834        elem = ElementTree.Element(tag)
835       
836        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
837                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
838       
839        statusCodeElem = StatusCodeElementTree.create(status.statusCode)
840        elem.append(statusCodeElem)
841       
842        return elem
843
844    @classmethod
845    def parse(cls, elem):
846        """Parse ElementTree element into a SAML Status object
847       
848        @type elem: ElementTree.Element
849        @param elem: subject as ElementTree XML element
850        @rtype: ndg.security.common.saml.Status
851        @return: SAML subject
852        """
853        if not ElementTree.iselement(elem):
854            raise TypeError("Expecting %r input type for parsing; got %r" %
855                            (ElementTree.Element, elem))
856
857        if QName.getLocalPart(elem.tag) != Status.DEFAULT_ELEMENT_LOCAL_NAME:
858            raise XMLObjectParseError('No "%s" element found' %
859                                      Status.DEFAULT_ELEMENT_LOCAL_NAME)
860           
861        if len(elem) != 1:
862            raise XMLObjectParseError("Expecting single StatusCode child "
863                                      "element for SAML Status element")
864           
865        status = Status()
866        status.statusCode = StatusCodeElementTree.parse(elem[0])
867       
868        return status
869   
870   
871class AttributeQueryElementTree(AttributeQuery, IssueInstantXMLObject):
872    """Represent a SAML Attribute Query in XML using ElementTree"""
873       
874    @classmethod
875    def create(cls, 
876               attributeQuery, 
877               **attributeValueElementTreeFactoryKw):
878        """Create an XML representation of the input SAML Attribute Query
879        object
880
881        @type attributeQuery: ndg.security.common.saml.AttributeQuery
882        @param attributeQuery: SAML Attribute Query
883        @rtype: ElementTree.Element
884        @return: Attribute Query as ElementTree XML element
885        """
886        if not isinstance(attributeQuery, AttributeQuery):
887            raise TypeError("Expecting %r class got %r" % (AttributeQuery, 
888                                                        type(attributeQuery)))
889           
890       
891        issueInstant = cls.datetime2Str(attributeQuery.issueInstant)
892        attrib = {
893            cls.ID_ATTRIB_NAME: attributeQuery.id,
894            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
895           
896            # Nb. Version is a SAMLVersion instance and requires explicit cast
897            cls.VERSION_ATTRIB_NAME: str(attributeQuery.version)
898        }
899       
900        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))
901        elem = ElementTree.Element(tag, **attrib)
902       
903        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
904                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
905       
906        issuerElem = IssuerElementTree.create(attributeQuery.issuer)
907        elem.append(issuerElem)
908
909        subjectElem = SubjectElementTree.create(attributeQuery.subject)
910        elem.append(subjectElem)
911
912        for attribute in attributeQuery.attributes:
913            # Factory enables support for multiple attribute types
914            attributeElem = AttributeElementTree.create(attribute,
915                                        **attributeValueElementTreeFactoryKw)
916            elem.append(attributeElem)
917       
918        return elem
919
920    @classmethod
921    def parse(cls, elem):
922        """Parse ElementTree element into a SAML AttributeQuery object
923       
924        @type elem: ElementTree.Element
925        @param elem: XML element containing the AttributeQuery
926        @rtype: ndg.security.common.saml.AttributeQuery
927        @return: AttributeQuery object
928        """
929        if not ElementTree.iselement(elem):
930            raise TypeError("Expecting %r input type for parsing; got %r" %
931                            (ElementTree.Element, elem))
932
933        if QName.getLocalPart(elem.tag) != cls.DEFAULT_ELEMENT_LOCAL_NAME:
934            raise XMLObjectParseError("No \"%s\" element found" %
935                                    cls.DEFAULT_ELEMENT_LOCAL_NAME)
936       
937        # Unpack attributes from top-level element
938        attributeValues = []
939        for attributeName in (cls.VERSION_ATTRIB_NAME,
940                              cls.ISSUE_INSTANT_ATTRIB_NAME,
941                              cls.ID_ATTRIB_NAME):
942            attributeValue = elem.attrib.get(attributeName)
943            if attributeValue is None:
944                raise XMLObjectParseError('No "%s" attribute found in "%s" '
945                                 'element' %
946                                 (attributeName,
947                                  cls.DEFAULT_ELEMENT_LOCAL_NAME))
948               
949            attributeValues.append(attributeValue)
950       
951        attributeQuery = AttributeQuery()
952        attributeQuery.version = SAMLVersion(attributeValues[0])
953        if attributeQuery.version != SAMLVersion.VERSION_20:
954            raise NotImplementedError("Parsing for %r is implemented for "
955                                      "SAML version %s only; version %s is " 
956                                      "not supported" % 
957                                      (cls,
958                                       SAMLVersion(SAMLVersion.VERSION_20),
959                                       SAMLVersion(attributeQuery.version)))
960           
961        attributeQuery.issueInstant = cls.str2Datetime(attributeValues[1])
962        attributeQuery.id = attributeValues[2]
963       
964        for childElem in elem:
965            localName = QName.getLocalPart(childElem.tag)
966            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
967                # Parse Issuer
968                attributeQuery.issuer = IssuerElementTree.parse(childElem)
969               
970            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
971                # Parse Subject
972                attributeQuery.subject = SubjectElementTree.parse(childElem)
973           
974            elif localName == Attribute.DEFAULT_ELEMENT_LOCAL_NAME:
975                attribute = AttributeElementTree.parse(childElem)
976                attributeQuery.attributes.append(attribute)
977            else:
978                raise XMLObjectParseError("Unrecognised AttributeQuery child "
979                                          "element \"%s\"" % localName)
980       
981        return attributeQuery
982       
983   
984class ResponseElementTree(Response, IssueInstantXMLObject):
985    """Represent a SAML Response in XML using ElementTree"""
986       
987    @classmethod
988    def create(cls, 
989               response, 
990               **attributeValueElementTreeFactoryKw):
991        """Create an XML representation of the input SAML Response
992        object
993
994        @type response: ndg.security.common.saml.Response
995        @param response: SAML Response
996        @rtype: ElementTree.Element
997        @return: Response as ElementTree XML element
998        """
999        if not isinstance(response, Response):
1000            raise TypeError("Expecting %r class, got %r" % (Response, 
1001                                                            type(response)))
1002           
1003       
1004        issueInstant = cls.datetime2Str(response.issueInstant)
1005        attrib = {
1006            cls.ID_ATTRIB_NAME: response.id,
1007            cls.ISSUE_INSTANT_ATTRIB_NAME: issueInstant,
1008            cls.IN_RESPONSE_TO_ATTRIB_NAME: response.inResponseTo,
1009           
1010            # Nb. Version is a SAMLVersion instance and requires explicit cast
1011            cls.VERSION_ATTRIB_NAME: str(response.version)
1012        }
1013       
1014        tag = str(QName.fromGeneric(cls.DEFAULT_ELEMENT_NAME))       
1015        elem = ElementTree.Element(tag, **attrib)
1016       
1017        ElementTree._namespace_map[cls.DEFAULT_ELEMENT_NAME.namespaceURI
1018                                   ] = cls.DEFAULT_ELEMENT_NAME.prefix
1019           
1020        issuerElem = IssuerElementTree.create(response.issuer)
1021        elem.append(issuerElem)
1022
1023        statusElem = StatusElementTree.create(response.status)       
1024        elem.append(statusElem)
1025
1026        for assertion in response.assertions:
1027            # Factory enables support for multiple attribute types
1028            assertionElem = AssertionElementTree.create(assertion,
1029                                        **attributeValueElementTreeFactoryKw)
1030            elem.append(assertionElem)
1031       
1032        return elem
1033
1034    @classmethod
1035    def parse(cls, elem):
1036        """Parse ElementTree element into a SAML Response object
1037       
1038        @type elem: ElementTree.Element
1039        @param elem: XML element containing the Response
1040        @rtype: ndg.security.common.saml.Response
1041        @return: Response object
1042        """
1043        if not ElementTree.iselement(elem):
1044            raise TypeError("Expecting %r input type for parsing; got %r" %
1045                            (ElementTree.Element, elem))
1046
1047        if QName.getLocalPart(elem.tag) != Response.DEFAULT_ELEMENT_LOCAL_NAME:
1048            raise XMLObjectParseError("No \"%s\" element found" %
1049                                      Response.DEFAULT_ELEMENT_LOCAL_NAME)
1050       
1051        # Unpack attributes from top-level element
1052        attributeValues = []
1053        for attributeName in (Response.VERSION_ATTRIB_NAME,
1054                              Response.ISSUE_INSTANT_ATTRIB_NAME,
1055                              Response.ID_ATTRIB_NAME,
1056                              Response.IN_RESPONSE_TO_ATTRIB_NAME):
1057            attributeValue = elem.attrib.get(attributeName)
1058            if attributeValue is None:
1059                raise XMLObjectParseError('No "%s" attribute found in "%s" '
1060                                          'element' %
1061                                         (attributeName,
1062                                          Response.DEFAULT_ELEMENT_LOCAL_NAME))
1063               
1064            attributeValues.append(attributeValue)
1065       
1066        response = Response()
1067        response.version = SAMLVersion(attributeValues[0])
1068        if response.version != SAMLVersion.VERSION_20:
1069            raise NotImplementedError("Parsing for %r is implemented for "
1070                                      "SAML version %s only; version %s is " 
1071                                      "not supported" % 
1072                                      (cls,
1073                                       SAMLVersion(SAMLVersion.VERSION_20),
1074                                       SAMLVersion(response.version)))
1075           
1076        response.issueInstant = cls.str2Datetime(attributeValues[1])
1077        response.id = attributeValues[2]
1078        response.inResponseTo = attributeValues[3]
1079       
1080        for childElem in elem:
1081            localName = QName.getLocalPart(childElem.tag)
1082            if localName == Issuer.DEFAULT_ELEMENT_LOCAL_NAME:
1083                # Parse Issuer
1084                response.issuer = IssuerElementTree.parse(childElem)
1085           
1086            elif localName == Status.DEFAULT_ELEMENT_LOCAL_NAME:
1087                # Get status of response
1088                response.status = StatusElementTree.parse(childElem)
1089               
1090            elif localName == Subject.DEFAULT_ELEMENT_LOCAL_NAME:
1091                # Parse Subject
1092                response.subject = SubjectElementTree.parse(childElem)
1093           
1094            elif localName == Assertion.DEFAULT_ELEMENT_LOCAL_NAME:
1095                response.assertions.append(
1096                                        AssertionElementTree.parse(childElem))
1097            else:
1098                raise XMLObjectParseError('Unrecognised Response child '
1099                                          'element "%s"' % localName)
1100       
1101        return response
1102
Note: See TracBrowser for help on using the repository browser.