source: TI12-security/trunk/python/ndg.security.common/ndg/security/common/zsi_utils/elementtreeproxy.py @ 4038

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/python/ndg.security.common/ndg/security/common/zsi_utils/elementtreeproxy.py@4038
Revision 4038, 11.2 KB checked in by pjkersha, 12 years ago (diff)

Completed ElementTree - ZSI integration for WS-Security SignatureHandler? but still problems with ET C14N for signature generation: C14N produces superfluous xmlns declarations.

Line 
1'''ZSI ElementTree ElementProxy class an interface to ZSI's ElementProxy
2
3Freely adapted from original by Joshua R. Boverhof, LBNL
4'''
5from ZSI.wstools.Namespaces import SCHEMA, XMLNS, SOAP
6from ZSI.wstools.Utility import MessageInterface
7from elementtree import ElementC14N, ElementTree
8from StringIO import StringIO
9
10from xml.dom import Node # Enable mimic of xml.dom behaviour
11
12
13class ElementProxyException(Exception):
14    ''''''
15
16class ElementTreeProxy(MessageInterface):
17    '''ElementTree wrapper
18    TODO: issue with "getPrefix"
19    '''
20    _soap_env_prefix = 'SOAP-ENV'
21    _soap_enc_prefix = 'SOAP-ENC'
22    _soap_env_nsuri = SOAP.ENV
23    _soap_enc_nsuri = SOAP.ENC
24
25    def __init__(self, message=None, etree=None, elem=None):
26        '''Initialize'''
27        self._etree = etree
28        self._elem = elem
29       
30        # Flag to enable correct behaviour for DOM childNodes
31        self._treeRoot = False
32       
33        if isinstance(etree, ElementTree.ElementTree) and elem is None:
34            self._elem = etree.getroot()
35        elif isinstance(elem, ElementTree.Element) and etree is None:
36            self._etree = ElementTree.ElementTree(element=elem)
37           
38       
39    def __str__(self):
40        return self.toString()
41       
42    def toString(self):
43        return self.canonicalize()
44   
45    #############################################
46    #Methods used in TypeCodes
47    #############################################
48    def createAppendElement(self, namespaceURI, localName, prefix=None):
49        '''Create a new element (namespaceURI,name), append it
50           to current node, and return the newly created node.
51        Keyword arguments:
52            namespaceURI -- namespace of element to create
53            localName -- local name of new element
54            prefix -- if namespaceURI is not defined, declare prefix.  defaults
55                to 'ns1' if left unspecified.
56        '''
57        if not prefix:
58            prefix = 'ns0'
59           
60        if namespaceURI: 
61                     
62            # Search for matching prefix
63            matchingPrefix = None
64            for elem in self._elem.getiterator():
65                for k, v in elem.items():
66                    if k.startswith("xmlns:") and v == namespaceURI:
67                        # Namespace declaration found
68                        matchingPrefix = k[6:]
69                        break
70
71            elem = ElementTree.Element("{%s}%s" % (namespaceURI, localName))
72
73            if not matchingPrefix:
74                matchingPrefix = prefix
75
76            elem.set("xmlns:%s" % matchingPrefix, namespaceURI)               
77        else:
78            assert prefix, "Prefix must be set - no namespaceURI was provided"
79
80            # Search for matching NS
81            for elem in self._etree.getiterator():
82                for k, v in elem.items():
83                    if k.startswith("xmlns:") and k[6:] == prefix:
84                        namespaceURI  = v
85                        break
86                   
87            elem = ElementTree.Element("{%s}%s:%s" % (namespaceURI, 
88                                                      prefix,
89                                                      localName))
90       
91           
92        self._elem.append(elem)
93       
94        eproxy = ElementTreeProxy(etree=self._etree, elem=elem)
95
96        return eproxy
97
98    def createAppendTextNode(self, pyobj):
99        '''TODO: obviously mixed text content cannot be accurately
100        represented via this interface.  Only 1 possible text node/element
101        '''
102        self._elem.text = pyobj
103
104    def createDocument(self, namespaceURI=SOAP.ENV, localName='Envelope'):
105
106        prefix = self._soap_env_prefix
107
108        self._elem = ElementTree.Element('{%s}%s' %(namespaceURI, localName))
109        self._etree = ElementTree.ElementTree(element=self._elem)
110        self._elem.set("xmlns:%s" % prefix, namespaceURI)
111       
112    def getElement(self, namespaceURI, localName):
113        for e in self._elem.getiterator():
114            l = e.tag.strip('{').split('}')
115            if not namespaceURI:
116                if len(l) == 1 and l[0] == localName:
117                    eproxy = ElementTreeProxy(elem=e, etree=self._etree)
118                    return eproxy
119            elif len(l) == 2 and l[0] == namespaceURI and l[1] == localName:
120                eproxy = ElementTreeProxy(elem=e, etree=self._etree)
121                return eproxy
122               
123        raise ElementProxyException,\
124            'No such element(%s,%s)' %(namespaceURI,localName)
125       
126    def getPrefix(self, namespaceURI):
127        '''TODO: this is not possible w/elementTree since namespace prefix
128        mappings aren't done until serialization.  completely abstracted out.
129        '''
130        raise NotImplementedError, "this func isn't going to work"
131       
132    def setAttributeNS(self, namespaceURI, localName, value):
133        '''
134        Keyword arguments:
135            namespaceURI -- namespace of attribute to create, None is for
136                attributes in no namespace.
137            localName -- local name of new attribute
138            value -- value of new attribute
139        ''' 
140        self._etree.attrib["{%s}%s" %(namespaceURI, localName)] = value
141
142    def getAttributeNS(self, namespaceURI, localName):
143       
144        return self._elem.get('{%s}%s' % (namespaceURI, localName))
145       
146    def setAttributeType(self, namespaceURI, localName):
147        '''xsi:type attribute, value must be a QName
148        '''
149        self.setAttributeNS(SCHEMA.XSI3, 'type', 
150            ElementTree.QName('{%s}%s' %(namespaceURI,localName))
151        )
152       
153    def setNamespaceAttribute(self, prefix, namespaceURI):
154        '''TODO: Not sure how to force this to be used by ElementTree
155        Keyword arguments:
156            prefix -- xmlns prefix
157            namespaceURI -- value of prefix
158        '''
159        #self._etree.attrib["xmlns:%s" %prefix] = namespaceURI
160        self._elem.set("xmlns:%s" % prefix, namespaceURI)
161       
162    def canonicalize(self, **kw):
163        # Make copy as this is a destructive process - attributes get deleted!
164#        etree = self._etree.copy()
165#        root = ElementTree.ElementTree(etree)
166#   
167#        root._scope = {}
168#        root._parent=dict((c, p) for p in etree.getiterator() for c in p)
169#   
170#        # build scope map
171#        for e in self._etree.getiterator():
172#            scope = []
173#            for k in e.keys():
174#                if k.startswith("xmlns:"):
175#                    # move xmlns prefix to scope map
176#                    scope.append((k[6:], e.get(k)))
177#                    del e.attrib[k]
178#            if scope:
179#                root._scope[e] = scope
180#   
181#        # Save as C14N
182#        f = StringIO()
183#        ElementC14N.write(root, f, **kw)
184#        c14n = f.getvalue()
185#        return c14n
186
187#        root = ElementTree.ElementTree(self._etree)
188#   
189#        root._scope = {}
190#        root._parent=dict((c, p) for p in self._etree.getiterator() for c in p)
191#   
192#        # build scope map
193#        for e in self._etree.getiterator():
194#            scope = []
195#            for k in e.keys():
196#                if k.startswith("xmlns:"):
197#                    # move xmlns prefix to scope map
198#                    scope.append((k[6:], e.get(k)))
199#                    del e.attrib[k]
200#            if scope:
201#                root._scope[e] = scope
202#   
203#        # Save as C14N
204#        f = StringIO()
205#        ElementC14N.write(root, f, **kw)
206#        c14n = f.getvalue()
207#        return c14n
208
209#        f = StringIO()
210#        ElementC14N.write(ElementC14N.build_scoped_tree(self._elem), f, **kw)
211#        c14n = f.getvalue()
212
213        f = StringIO()
214
215        # Check that namespace scope has been added - this will be the case
216        # for a parsed message but not true for a document created in memory.
217        # In the latter case a call to build the scope is required
218        if hasattr(self._etree, '_scope'):
219            ElementC14N.write(self._etree, f, **kw)
220        else:
221            ElementC14N.write(_build_scoped_tree(self._elem), f, 
222                              **kw)
223           
224        c14n = f.getvalue()
225
226        return c14n
227
228   
229    def evaluate(self, expression, processorNss=None):
230        elemList = self._etree.findall(expression, namespaces=processorNss)
231           
232        return [ElementTreeProxy(elem=elem, etree=self._etree) \
233                for elem in elemList]
234   
235    # Methods to satisfy ParsedSoap interface       
236    def fromString(self, input):
237        '''Required by ParsedSoap to parse a string'''
238       
239        # Use ElementC14N.parse as fromstring doesn't create a scope dict
240#        self._elem = ElementTree.fromstring(input)
241#        self._etree = ElementTree.ElementTree(self._elem)
242        fInput = StringIO()
243        fInput.write(input)
244        fInput.seek(0)
245       
246        self._etree = ElementC14N.parse(fInput)
247        self._elem = self._etree.getroot()
248        self._treeRoot = True
249        return self
250   
251    def _getChildNodes(self):
252        '''childNodes property required by ParsedSoap'''
253        if self._elem is None:
254            return []
255       
256        # Check for top of tree
257        if self._treeRoot: 
258            # Return a copy of the top level element with _treeRoot set to
259            # False so that the next time this method is called the children of
260            # the top level element will be returned instead
261            return [ElementTreeProxy(etree=self._etree)]
262        else:
263            return [ElementTreeProxy(elem=elem, etree=self._etree) \
264                    for elem in list(self._elem)]
265               
266    childNodes = property(fget=_getChildNodes)
267   
268    def _getNodeType(self):
269        '''Minimal implementation to mimic behaviour of xml.dom.Node for
270        ParsedSoap interface'''
271        return Node.ELEMENT_NODE
272   
273    nodeType = property(fget=_getNodeType)
274   
275    def _getLocalName(self):
276        '''Parse localName from element tag of form {NS}localName'''
277        return self._elem.tag.split('}')[-1]
278   
279    localName = property(fget=_getLocalName)
280   
281    def _getNamespaceURI(self):
282        '''Parse NS from element tag of form {NS}localName'''
283        return self._elem.tag.replace('{','').split('}')[0]
284   
285    namespaceURI = property(fget=_getNamespaceURI)
286   
287    def _getAttributes(self):
288        '''Mimic attributes DOM attribute but note XML namespace declarations
289        are not included in ET
290       
291        TODO: alter parser to keep xmlns declarations'''
292        return []
293   
294    attributes = property(fget=_getAttributes)
295
296from copy import deepcopy
297
298def _build_scoped_tree(_elem):
299   
300    # Make a copy because attributes are to be deleted.
301    elem = deepcopy(_elem)
302    # Deep copy misses out 'attrib' Element attribute
303    import pdb;pdb.set_trace()
304    for e, _e in zip(elem.getiterator(), _elem.getiterator()):
305        e.attrib = _e.attrib.copy()
306       
307    root = ElementTree.ElementTree(elem)
308
309    # build scope map
310    root._scope = {}
311    for e in elem.getiterator():
312        scope = []
313        for k in e.keys():
314            if k.startswith("xmlns:"):
315                # move xmlns prefix to scope map
316                scope.append((k[6:], e.get(k)))
317                del e.attrib[k]
318        if scope:
319            root._scope[e] = scope
320    # build parent map
321    root._parent = dict((c, p) for p in elem.getiterator() for c in p)
322
323    return root
Note: See TracBrowser for help on using the repository browser.