# Copyright (C) 2021 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
import base64
import urllib.parse
import pytest
from passerelle.apps.soap.models import SOAPConnector
from . import utils
pytestmark = pytest.mark.django_db
class SOAP11:
VERSION = '1.1'
ENDPOINT_URL = 'https://www.examples.com/SayHello/'
WSDL_CONTENT = '''\
WSDL File for HelloService
'''
WSDL_URL = 'https://example.com/service.wsdl'
SOAP_RESPONSE = '''\
Hello
John!
'''
INPUT_SCHEMA = {
'properties': {
'firstName': {
'description': '{http://www.examples.com/wsdl/HelloService.wsdl}firstName',
'properties': {
'string': {'items': {'type': 'string', 'description': 'xsd:string'}, 'type': 'array'},
},
'required': ['string'],
'type': 'object',
},
'lastName': {'type': 'string', 'description': 'xsd:string'},
},
'required': ['firstName', 'lastName'],
'type': 'object',
}
OUTPUT_SCHEMA = {
'properties': {
'greeting': {'type': 'string', 'description': 'xsd:string'},
'who': {'type': 'string', 'description': 'xsd:string'},
},
'required': ['greeting', 'who'],
'type': 'object',
}
INPUT_DATA = {
'firstName/string/0': 'John',
'firstName/string/1': 'Bill',
'lastName': 'Doe',
}
OUTPUT_DATA = {
'greeting': 'Hello',
'who': 'John!',
}
VALIDATION_ERROR = 'Missing element firstName (sayHello.firstName)'
class SOAP12(SOAP11):
VERSION = '1.2'
ENDPOINT_URL = 'https://www.examples.com/SayHello/'
WSDL_CONTENT = f'''\
'''
SOAP_RESPONSE = '''\
Hello
John!
'''
INPUT_SCHEMA = {
'type': 'object',
'properties': {
'firstName': {'type': 'array', 'items': {'type': 'string', 'description': 'xsd:string'}},
'lastName': {'type': 'string', 'description': 'xsd:string'},
},
'required': ['firstName', 'lastName'],
'description': '{urn:examples:helloservice}sayHello',
}
OUTPUT_SCHEMA = {
'description': '{urn:examples:helloservice}sayHelloResponse',
'properties': {
'greeting': {'type': 'string', 'description': 'xsd:string'},
'who': {
'type': 'array',
'items': {'type': 'string', 'description': 'xsd:string'},
},
},
'required': ['greeting', 'who'],
'type': 'object',
}
INPUT_DATA = {
'firstName/0': 'John',
'firstName/1': 'Bill',
'lastName': 'Doe',
}
OUTPUT_DATA = {
'greeting': 'Hello',
'who': ['John!'],
}
VALIDATION_ERROR = 'Expected at least 1 items (minOccurs check) 0 items found. (sayHello.firstName)'
class BrokenSOAP12(SOAP12):
WSDL_CONTENT = SOAP12.WSDL_CONTENT[-100:] # truncate the WSDL to break it
@pytest.fixture(params=[SOAP11, SOAP12])
def soap(request):
p = request.param()
with utils.mock_url(p.WSDL_URL, response=p.WSDL_CONTENT):
with utils.mock_url(p.ENDPOINT_URL, response=p.SOAP_RESPONSE) as mock:
p.endpoint_mock = mock
yield p
class TestManage:
@pytest.fixture
def app(self, app, admin_user):
from .test_manager import login
login(app)
return app
def test_homepage(self, app, connector, soap):
response = app.get(f'/soap/{connector.slug}/')
assert 'Method sayHello' in response
@pytest.mark.parametrize('soap', [BrokenSOAP12], indirect=True)
def test_homepage_broken_wsdl(self, app, connector, soap):
response = app.get(f'/soap/{connector.slug}/')
response = app.get(f'/soap/{connector.slug}/')
assert response.pyquery('.down')
@pytest.fixture
def connector(db, soap):
return utils.setup_access_rights(
SOAPConnector.objects.create(
slug='test', wsdl_url=soap.WSDL_URL, zeep_strict=True, zeep_xsd_ignore_sequence_order=False
)
)
def test_schemas(connector, soap):
assert list(connector.operations_and_schemas) == [('sayHello', soap.INPUT_SCHEMA, soap.OUTPUT_SCHEMA)]
def test_say_hello_method_validation_error(connector, soap, app):
resp = app.get('/soap/test/method/sayHello/')
assert resp.json == {
"err": 1,
"err_class": "passerelle.utils.soap.SOAPError",
"err_desc": soap.VALIDATION_ERROR,
"data": None,
}
def test_say_hello_method_ok_get(connector, app, caplog, soap):
resp = app.get('/soap/test/method/sayHello/?' + urllib.parse.urlencode(soap.INPUT_DATA))
assert '>John<' in soap.endpoint_mock.handlers[0].call['requests'][-1].body.decode()
assert '>Bill<' in soap.endpoint_mock.handlers[0].call['requests'][-1].body.decode()
assert '>Doe<' in soap.endpoint_mock.handlers[0].call['requests'][-1].body.decode()
assert resp.json == {'data': soap.OUTPUT_DATA, 'err': 0}
def test_say_hello_method_ok_get_with_signature(connector, app, caplog, soap):
resp = app.get('/soap/test/method/sayHello/?' + urllib.parse.urlencode(soap.INPUT_DATA) + '&signature=1')
assert resp.json['err'] == 0
def test_say_hello_method_ok_post_json(connector, app, caplog, soap):
resp = app.post_json('/soap/test/method/sayHello/', params=soap.INPUT_DATA)
assert '>John<' in soap.endpoint_mock.handlers[0].call['requests'][-1].body.decode()
assert '>Bill<' in soap.endpoint_mock.handlers[0].call['requests'][-1].body.decode()
assert '>Doe<' in soap.endpoint_mock.handlers[0].call['requests'][-1].body.decode()
assert resp.json == {'data': soap.OUTPUT_DATA, 'err': 0}
@pytest.mark.parametrize('soap', [SOAP12], indirect=True)
class TestAuthencation:
def test_basic_auth(self, connector, app, caplog, soap):
connector.basic_auth_username = 'username'
connector.basic_auth_password = 'password'
connector.save()
app.post_json('/soap/test/method/sayHello/', params=soap.INPUT_DATA)
assert (
base64.b64decode(
soap.endpoint_mock.handlers[0].call['requests'][1].headers['Authorization'].split()[1]
)
== b'username:password'
)
assert b'wsse:UsernameToken' not in soap.endpoint_mock.handlers[0].call['requests'][1].body
def test_username_token(self, connector, app, caplog, soap):
connector.zeep_wsse_username = 'username'
connector.zeep_wsse_password = 'password'
connector.save()
app.post_json('/soap/test/method/sayHello/', params=soap.INPUT_DATA)
assert 'Authorization' not in soap.endpoint_mock.handlers[0].call['requests'][1].headers
assert b'wsse:UsernameToken' in soap.endpoint_mock.handlers[0].call['requests'][1].body