From b923f49a74b02de3e5417d13f07d341880a0d176 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Tue, 15 Oct 2019 10:39:01 +0200
Subject: [PATCH] utils/soap: upgrade zeep soap library version (#35370)

---
 passerelle/utils/soap.py | 14 +++++++++++++-
 setup.py                 |  2 +-
 tests/data/soap.wsdl     |  5 +++--
 tests/test_iparapheur.py | 17 ++++++++++++-----
 tests/test_soap.py       | 29 +++++++++++++++++++++++++++++
 5 files changed, 58 insertions(+), 9 deletions(-)

diff --git a/passerelle/utils/soap.py b/passerelle/utils/soap.py
index 6b28d942..11c1cdbb 100644
--- a/passerelle/utils/soap.py
+++ b/passerelle/utils/soap.py
@@ -19,6 +19,7 @@ from requests import RequestException
 from zeep import Client
 from zeep.cache import InMemoryCache
 from zeep.transports import Transport
+from zeep import __version__ as zeep_version
 
 from django.utils.six.moves.urllib import parse as urlparse
 
@@ -38,7 +39,18 @@ class SOAPClient(Client):
         wsdl_url = kwargs.pop('wsdl_url', None) or resource.wsdl_url
         transport_class = getattr(resource, 'soap_transport_class', SOAPTransport)
         transport = transport_class(resource, wsdl_url, session=resource.requests, cache=InMemoryCache())
-        super(SOAPClient, self).__init__(wsdl_url, transport=transport, **kwargs)
+
+        if zeep_version < '3':
+            super(SOAPClient, self).__init__(
+                wsdl_url, transport=transport, **kwargs)
+        else:
+            from zeep import Settings
+            kept_kwargs = {}
+            for key in 'wsse', 'service_name', 'port_name', 'plugins':
+                kept_kwargs[key] = kwargs.pop(key, None)
+            settings = Settings(**kwargs)
+            super(SOAPClient, self).__init__(
+                wsdl_url, transport=transport, settings=settings, **kept_kwargs)
 
 
 class SOAPTransport(Transport):
diff --git a/setup.py b/setup.py
index 617f95bc..7f373111 100755
--- a/setup.py
+++ b/setup.py
@@ -104,7 +104,7 @@ setup(name='passerelle',
             'python-dateutil',
             'Pillow',
             'jsonschema < 3.1',
-            'zeep < 3.0',
+            'zeep',
             'pycrypto',
             'unidecode',
             'paramiko',
diff --git a/tests/data/soap.wsdl b/tests/data/soap.wsdl
index 57832709..399af5a1 100644
--- a/tests/data/soap.wsdl
+++ b/tests/data/soap.wsdl
@@ -48,9 +48,10 @@
       </element>
       <element name="TradePrice">
         <complexType>
-          <all>
+          <sequence>
+            <element name="skipMe" type="float"/>
             <element name="price" type="float"/>
-          </all>
+          </sequence>
         </complexType>
       </element>
       <complexType name="account">
diff --git a/tests/test_iparapheur.py b/tests/test_iparapheur.py
index eefa61bd..1fdace2a 100644
--- a/tests/test_iparapheur.py
+++ b/tests/test_iparapheur.py
@@ -8,6 +8,7 @@ import base64
 import xml.etree.ElementTree as ET
 from dateutil import parser
 from requests import Response
+from zeep import __version__ as zeep_version
 
 from requests.exceptions import ConnectionError
 from django.core.urlresolvers import reverse
@@ -41,6 +42,12 @@ def conn():
                     resource_pk=conn.pk)
     return conn
 
+def assert_invalid_xml(err_desc):
+    if zeep_version < '3':
+        assert "Server returned HTTP status 200 (<nada>)" in err_desc
+    else:
+        assert "Server returned response (200) with invalid XML" in err_desc
+
 def xmlmime():
     return os.path.join(os.path.dirname(__file__), 'data','xmlmime.xml')
 
@@ -187,7 +194,7 @@ def test_create_file(mocked_post, mocked_get, app, conn):
     assert (BASE_URL,) == mocked_post.call_args[0]
     assert resp.json['err'] == 1
     assert 'zeep.exceptions.TransportError' in resp.json['err_class']
-    assert 'Server returned HTTP status 200 (<nada>)' in resp.json['err_desc']
+    assert_invalid_xml(resp.json['err_desc'])
 
     # Unknown value for "visibility"
     err_data = data.copy()
@@ -211,7 +218,7 @@ def test_create_file(mocked_post, mocked_get, app, conn):
     assert (BASE_URL,) == mocked_post.call_args[0]
     assert resp.json['err'] == 1
     assert 'zeep.exceptions.TransportError' in resp.json['err_class']
-    assert 'Server returned HTTP status 200 (<nada>)' in resp.json['err_desc']
+    assert_invalid_xml(resp.json['err_desc'])
 
 @mock.patch('passerelle.utils.Request.get', side_effect=iph_mocked_get)
 @mock.patch('passerelle.utils.Request.post')
@@ -300,7 +307,7 @@ def test_get_file_status(mocked_post, mocked_get, app, conn):
     assert resp.json['err'] == 1
     #assert 'zeep.exceptions.TransportError' in resp.json['err_class']
     assert 'passerelle.utils.jsonresponse.APIError' in resp.json['err_class']
-    assert 'Server returned HTTP status 200 (<nada>)' in resp.json['err_desc']
+    assert_invalid_xml(resp.json['err_desc'])
 
 @mock.patch('passerelle.utils.Request.get', side_effect=iph_mocked_get)
 @mock.patch('passerelle.utils.Request.post')
@@ -325,7 +332,7 @@ def test_get_file(mocked_post, mocked_get, app, conn):
     assert resp.body == 'Test Document'
 
     # KO
-    soap_response = """<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><CreerDossierResponse xmlns="http://www.adullact.org/spring-ws/iparapheur/1.0" xmlns:xmime="http://www.w3.org/2005/05/xmlmime"><MessageRetour><codeRetour>KO</codeRetour><message>KOmessage</message><severite>INFO</severite></MessageRetour></CreerDossierResponse></S:Body></S:Envelope>"""
+    soap_response = """<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><GetDossierResponse xmlns="http://www.adullact.org/spring-ws/iparapheur/1.0" xmlns:xmime="http://www.w3.org/2005/05/xmlmime"><MessageRetour><codeRetour>KO</codeRetour><message>KOmessage</message><severite>INFO</severite></MessageRetour></GetDossierResponse></S:Body></S:Envelope>"""
     response._content = soap_response
     mocked_post.return_value = response
     resp = app.get(url, status=500)
@@ -334,7 +341,7 @@ def test_get_file(mocked_post, mocked_get, app, conn):
     assert resp.json['err_desc'] == 'KOmessage'
 
     # unknown response
-    soap_response = """<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><CreerDossierResponse xmlns="http://www.adullact.org/spring-ws/iparapheur/1.0" xmlns:xmime="http://www.w3.org/2005/05/xmlmime"></CreerDossierResponse></S:Body></S:Envelope>"""
+    soap_response = """<?xml version='1.0' encoding='UTF-8'?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body><GetDossierResponse xmlns="http://www.adullact.org/spring-ws/iparapheur/1.0" xmlns:xmime="http://www.w3.org/2005/05/xmlmime"></GetDossierResponse></S:Body></S:Envelope>"""
     response._content = soap_response
     mocked_post.return_value = response
     resp = app.get(url, status=500)
diff --git a/tests/test_soap.py b/tests/test_soap.py
index 7752707f..d336fa8f 100644
--- a/tests/test_soap.py
+++ b/tests/test_soap.py
@@ -1,5 +1,8 @@
+import pytest
+import mock
 import requests
 from zeep.plugins import Plugin
+from zeep.exceptions import XMLParseError
 
 from passerelle.utils.soap import SOAPClient
 
@@ -30,3 +33,29 @@ def test_soap_client():
     assert client.transport.session == soap_resource.requests
     assert client.transport.cache
     assert client.plugins == plugins
+
+@mock.patch('requests.sessions.Session.post')
+def test_disable_strict_mode(mocked_post):
+    response = requests.Response()
+    response.status_code = 200
+    response._content = '''<?xml version='1.0' encoding='utf-8'?>
+<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap-env:Body>
+    <ns0:TradePrice xmlns:ns0="http://example.com/stockquote.xsd">
+      <price>4.20</price>
+    </ns0:TradePrice>
+  </soap-env:Body>
+</soap-env:Envelope>'''
+    mocked_post.return_value = response
+
+    soap_resource = SOAPResource()
+    client = SOAPClient(soap_resource)
+    with pytest.raises(
+            XMLParseError, match="Unexpected element u'price', expected u'skipMe'"):
+        client.service.GetLastTradePrice(tickerSymbol='banana')
+
+    client = SOAPClient(soap_resource, strict=False)
+    result = client.service.GetLastTradePrice(tickerSymbol='banana')
+    assert len(result) == 2
+    assert result['skipMe'] == None
+    assert result['price'] == 4.2
-- 
2.23.0

