Permalink
Browse files

Merge pull request #21 from ombre42/basic-auth

Basic auth support to `Create Soap Client`
  • Loading branch information...
2 parents 5ddd20b + 6e3e0b0 commit 640a3cc70cf3b1087f931840171b1b88a9db1c17 @ombre42 committed Oct 5, 2014
@@ -23,7 +23,8 @@
class _ClientManagementKeywords(object):
- def create_soap_client(self, url_or_path, alias=None, autoblend=False, timeout='90 seconds'):
+ def create_soap_client(self, url_or_path, alias=None, autoblend=False, timeout='90 seconds', username=None,
+ password=None, auth_type='STANDARD'):
"""Loads a WSDL from the given URL/path and creates a Suds SOAP client.
Returns the index of this client instance which can be used later to
@@ -33,6 +34,10 @@ def create_soap_client(self, url_or_path, alias=None, autoblend=False, timeout='
for switching between clients (just as index can be used). See `Switch
Soap Client` for more details.
+ `username` and `password` are needed if the WSDL is on a server
+ requiring basic authentication. `auth_type` selects the authentication
+ scheme to use. See `Set Http Authentication` for more information.
+
Autoblend ensures that the schema(s) defined within the WSDL import
each other.
@@ -46,6 +51,10 @@ def create_soap_client(self, url_or_path, alias=None, autoblend=False, timeout='
url = self._get_url(url_or_path)
autoblend = to_bool(autoblend)
kwargs = {'autoblend': autoblend}
+ if username:
+ password = password if password is not None else ""
+ transport = self._get_transport(auth_type, username, password)
+ kwargs['transport'] = transport
imports = self._imports
if imports:
self._log_imports()
@@ -119,19 +119,11 @@ def set_http_authentication(self, username, password, type='STANDARD'):
will only send credentials to the server upon request (HTTP/1.0 401
Authorization Required) by the server only. Type ALWAYS_SEND will
cause an Authorization header to be sent in every request. Type NTLM
- requires the python-ntlm package to be installed, which is not
- packaged with Suds or SudsLibrary.
+ is a Microsoft proprietary authentication scheme that requires the
+ python-ntlm package to be installed, which is not packaged with Suds
+ or SudsLibrary.
"""
- classes = {
- 'STANDARD': HttpAuthenticated,
- 'ALWAYS_SEND': AlwaysSendTransport,
- 'NTLM': WindowsHttpAuthenticated
- }
- try:
- _class = classes[type.upper()]
- except KeyError:
- raise ValueError("'%s' is not a supported type." % type)
- transport = _class(username=username, password=password)
+ transport = self._get_transport(type, username=username, password=password)
self._client().set_options(transport=transport)
def set_location(self, url, service=None, names=None):
@@ -236,3 +228,16 @@ def _set_external_option(self, name, value):
old_value = self._external_options[self._client()].get(name, None)
self._external_options[self._client()][name] = value
return old_value
+
+ def _get_transport(self, auth_type, username, password):
+ classes = {
+ 'STANDARD': HttpAuthenticated,
+ 'ALWAYS_SEND': AlwaysSendTransport,
+ 'NTLM': WindowsHttpAuthenticated
+ }
+ try:
+ _class = classes[auth_type.upper().strip()]
+ except KeyError:
+ raise ValueError("'%s' is not a supported authentication type." % auth_type)
+ transport = _class(username=username, password=password)
+ return transport
@@ -0,0 +1,4 @@
+*** Settings ***
+Suite Setup Start Services
+Suite Teardown Stop Services
+Resource resource.txt
@@ -0,0 +1,34 @@
+*** Settings ***
+Resource resource.txt
+
+*** Test Cases ***
+Http Authentication
+ Create Soap Client ${TEST WSDL URL}
+ Set Location ${SECURE TEST URL}
+ Run Keyword And Expect Error (401, u'Authentication Required') Call Soap Method theAnswer
+ Set Http Authentication bob bob STANDARD
+ ${answer} Call Soap Method theAnswer
+ Should Be Equal ${answer} ${42}
+ Create Soap Client ${TEST WSDL URL}
+ Set Http Authentication bob foo ALWAYS_send
+ ${sum} Call Soap Method theAnswer
+ ${request} Get Sent Request
+ # auth not needed, but should have been sent anyways
+ Should Be Equal As Strings ${request.headers['Authorization']} Basic Ym9iOmZvbw==
+
+Secured WSDL
+ Run Keyword And Expect Error *401* Create Soap Client ${SECURE TEST WSDL URL} # sanity check
+ Create Soap Client ${SECURE TEST WSDL URL} username=beth password=beth
+ ${answer} Call Soap Method theAnswer
+ Should Be Equal ${answer} ${42}
+
+Unsecure WSDL With Secured Import
+ [Documentation] To test that the transport is used on imports, use a WSDL that does not require authentication that imports a document that does require authentication.
+ Create Soap Client ${WSDL DIR}/TestServices_secured_import.wsdl username=beth password=beth
+ ${answer} Call Soap Method theAnswer
+ Should Be Equal ${answer} ${42}
+
+Bad Authentication Type
+ Run Keyword And Expect Error ValueError: 'bad' is not a supported authentication type. Create Soap Client ${TEST WSDL URL} username=bob password=foo auth_type=bad
+ Create Soap Client ${TEST WSDL URL}
+ Run Keyword And Expect Error ValueError: 'bad' is not a supported authentication type. Set Http Authentication bob foo bad
@@ -1,6 +1,4 @@
*** Settings ***
-Suite Setup Start Services
-Suite Teardown Stop Services
Resource resource.txt
*** Variables ***
@@ -200,18 +198,6 @@ Return ComplexType and Alter
${first name} Get Wsdl Object Attribute ${person} first-name
Should Be Equal As Strings ${first name} Bob
-Http Authentication
- Create Soap Client ${CURDIR}/../resources/wsdls/Calculator.wsdl
- Set Http Authentication bob foo STANDARD
- ${sum} Call Soap Method add 1 41
- ${request} Get Sent Request
- Dictionary Should Not Contain Key ${request.headers} Authorization
- Set Http Authentication bob foo ALWAYS_send
- ${sum} Call Soap Method add 1 41
- ${request} Get Sent Request
- Should Be Equal As Strings ${request.headers['Authorization']} Basic Ym9iOmZvbw==
- Run Keyword And Expect Error ValueError: 'bad' is not a supported type. Set Http Authentication bob foo bad
-
Set Location
Create Soap Client ${TEST WSDL URL}
Set Location http://localhost:8080/badpath
@@ -9,3 +9,7 @@ Library ../resources/Util.py
*** Variables ***
${CALCULATOR WSDL URL} http://localhost:8080/Calculator/soap11/description
${TEST WSDL URL} http://localhost:8080/TestService/soap11/description
+${SECURE TEST WSDL URL} http://localhost:8080/secure/TestService/soap11/description
+${SECURE TEST URL} http://localhost:8080/secure/TestService/soap11
+${WSDL DIR} http://localhost:8080/wsdls
+${SECURE WSDL DIR} http://localhost:8080/secure/wsdls
@@ -1,6 +1,4 @@
*** Settings ***
-Suite Setup Start Services
-Suite Teardown Stop Services
Test Setup Create Soap Client ${TEST WSDL URL}
Resource resource.txt
@@ -1,37 +1,107 @@
# -*- coding: utf-8 -*-
from ladon.server.wsgi import LadonWSGIApplication
+from werkzeug.wsgi import SharedDataMiddleware
import wsgiref.simple_server
-from os.path import abspath,dirname,join
+from wsgiref.util import shift_path_info
+from os.path import abspath, dirname, join
import subprocess
+import os
+from tempfile import TemporaryFile
-scriptdir = dirname(abspath(__file__))
-service_modules = ['calculator','testservice']
+THIS_DIR = dirname(abspath(__file__))
+service_modules = ['calculator', 'testservice']
port = 8080
-class LadonServer():
+
+class Auth(object):
+ """Applies basic authentication where authentication is successful if the user name and the password are the same.
+
+ Taken from http://eddmann.com/posts/creating-a-basic-auth-wsgi-middleware-in-python/
+ """
+ def __init__(self, app):
+ self._app = app
+
+ def __call__(self, environ, start_response):
+ if self._authenticated(environ.get('HTTP_AUTHORIZATION')):
+ return self._app(environ, start_response)
+ return self._login(environ, start_response)
+
+ def _authenticated(self, header):
+ from base64 import b64decode
+ if not header:
+ return False
+ _, encoded = header.split(None, 1)
+ decoded = b64decode(encoded).decode('UTF-8')
+ username, password = decoded.split(':', 1)
+ return username == password
+
+ def _login(self, environ, start_response):
+ start_response('401 Authentication Required',
+ [('Content-Type', 'text/html'),
+ ('WWW-Authenticate', 'Basic realm="Login"')])
+ return [b'Login']
+
+
+class Director(object):
+ """Directs requests a root app or other spps mapped to a single path part above root."""
+ def __init__(self, root_app, **other_apps):
+ self._root_app = root_app
+ self._other_apps = dict([(key.lower(), value) for key, value in other_apps.iteritems()])
+
+ def __call__(self, environ, start_response):
+ orig_path = environ.get('PATH_INFO')
+ orig_script_name = environ.get('SCRIPT_NAME')
+ path_part = (shift_path_info(environ) or '').lower()
+ if path_part in self._other_apps:
+ return self._other_apps[path_part](environ, start_response)
+ else:
+ environ['PATH_INFO'] = orig_path
+ environ['SCRIPT_NAME'] = orig_script_name
+ return self._root_app(environ, start_response)
+
+
+class WebServer(object):
def start(self):
- application = self._create_application()
- self.server = wsgiref.simple_server.make_server('', port, application)
- self.server.serve_forever()
+ unsecured = self._create_web_services('Unsecured services')
+ unsecured = self._add_shared_data(unsecured)
+ secured = self._create_web_services('Secured services')
+ secured = self._add_shared_data(secured)
+ secured = Auth(secured)
+ application = Director(unsecured, secure=secured)
+ server = wsgiref.simple_server.make_server('', port, application)
+ server.serve_forever()
- def _create_application(self):
+ def _create_web_services(self, name):
return LadonWSGIApplication(
service_modules,
- [join(scriptdir,'services')],
- catalog_name = 'Services for testing SudsLibrary',
- catalog_desc = 'Ladon cannot cover many use cases for Suds, but it is easy to set up and generates WSDLs.')
+ [join(THIS_DIR, 'services')],
+ catalog_name=name,
+ catalog_desc='Ladon cannot cover many use cases for Suds, but it is easy to set up and generates WSDLs.')
+
+ def _add_shared_data(self, application):
+ """Exposes the files in wsdls folder"""
+ mapping = {'/wsdls': os.path.join(os.path.dirname(__file__), 'wsdls')}
+ return SharedDataMiddleware(application, mapping)
+
class TestWebServices(object):
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
def start_services(self):
- self.server = subprocess.Popen(['python', abspath(__file__)])
+ self.server = subprocess.Popen(['python', abspath(__file__)], stdout=TemporaryFile(), stderr=subprocess.STDOUT)
def stop_services(self):
self.server.kill()
-if __name__=='__main__':
- print("\nExample services are running on port %(port)s.\nView browsable API at http://localhost:%(port)s\n" % {'port':port})
- LadonServer().start()
+if __name__ == '__main__':
+ stuff = [
+ ('Unsecured web services', 'http://localhost:%(port)s/'),
+ ('Unsecured files', 'http://localhost:%(port)s/wsdls/'),
+ ('Secured web services', 'http://localhost:%(port)s/secure/'),
+ ('Secured files', 'http://localhost:%(port)s/secure/wsdls/')
+ ]
+ for item in stuff:
+ print item[0] + ' : ' + item[1] % {'port': port}
+ WebServer().start()
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsd:schema targetNamespace="http://tempuri.org/schemas" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:complexType name="Person">
+ <xsd:sequence>
+ <xsd:element maxOccurs="1" minOccurs="1" name="first-name" type="xsd:string"/>
+ <xsd:element maxOccurs="1" minOccurs="1" name="last-name" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+</xsd:schema>
@@ -0,0 +1,76 @@
+<!-- requires http auth to fetch imported schema -->
+<wsdl:definitions name="TestService" targetNamespace="urn:TestService" xmlns:xsd1="http://tempuri.org/schemas" xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:tns="urn:TestService" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <wsdl:types>
+ <xsd:schema targetNamespace="urn:TestService">
+ <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
+ <xsd:import namespace="http://tempuri.org/schemas" schemaLocation="http://localhost:8080/secure/wsdls/ImportedType.xsd"/>
+ </xsd:schema>
+ </wsdl:types>
+ <wsdl:message name="complexTypeArgument">
+ <wsdl:part name="person" type="xsd1:Person"/>
+ </wsdl:message>
+ <wsdl:message name="complexTypeArgumentResponse">
+ <wsdl:part name="result" type="xsd:string"/>
+ </wsdl:message>
+ <wsdl:message name="returnComplexType">
+ <wsdl:part name="first-name" type="xsd:string"/>
+ <wsdl:part name="last-name" type="xsd:string"/>
+ </wsdl:message>
+ <wsdl:message name="returnComplexTypeResponse">
+ <wsdl:part name="result" type="xsd1:Person"/>
+ </wsdl:message>
+ <wsdl:message name="theAnswer"/>
+ <wsdl:message name="theAnswerResponse">
+ <wsdl:part name="result" type="xsd:long"/>
+ </wsdl:message>
+ <wsdl:portType name="TestServicePortType">
+ <wsdl:operation name="complexTypeArgument">
+ <wsdl:input message="tns:complexTypeArgument"/>
+ <wsdl:output message="tns:complexTypeArgumentResponse"/>
+ </wsdl:operation>
+ <wsdl:operation name="returnComplexType">
+ <wsdl:input message="tns:returnComplexType"/>
+ <wsdl:output message="tns:returnComplexTypeResponse"/>
+ </wsdl:operation>
+ <wsdl:operation name="theAnswer">
+ <wsdl:input message="tns:theAnswer"/>
+ <wsdl:output message="tns:theAnswerResponse"/>
+ </wsdl:operation>
+ </wsdl:portType>
+ <wsdl:binding name="TestService" type="tns:TestServicePortType">
+ <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
+ <wsdl:operation name="complexTypeArgument">
+ <soap:operation soapAction="http://localhost:8080/TestService/soap11/complexTypeArgument" style="rpc"/>
+ <wsdl:input>
+ <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:TestService" use="encoded"/>
+ </wsdl:input>
+ <wsdl:output>
+ <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:TestService" use="encoded"/>
+ </wsdl:output>
+ </wsdl:operation>
+ <wsdl:operation name="returnComplexType">
+ <soap:operation soapAction="http://localhost:8080/TestService/soap11/returnComplexType" style="rpc"/>
+ <wsdl:input>
+ <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:TestService" use="encoded"/>
+ </wsdl:input>
+ <wsdl:output>
+ <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:TestService" use="encoded"/>
+ </wsdl:output>
+ </wsdl:operation>
+ <wsdl:operation name="theAnswer">
+ <soap:operation soapAction="http://localhost:8080/TestService/soap11/theAnswer" style="rpc"/>
+ <wsdl:input>
+ <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:TestService" use="encoded"/>
+ </wsdl:input>
+ <wsdl:output>
+ <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" namespace="urn:TestService" use="encoded"/>
+ </wsdl:output>
+ </wsdl:operation>
+ </wsdl:binding>
+ <wsdl:service name="TestService">
+ <wsdl:documentation>Ladon generated service definition</wsdl:documentation>
+ <wsdl:port binding="tns:TestService" name="TestService">
+ <soap:address location="http://localhost:8080/TestService/soap11"/>
+ </wsdl:port>
+ </wsdl:service>
+</wsdl:definitions>

0 comments on commit 640a3cc

Please sign in to comment.