pax_global_header00006660000000000000000000000064124563061710014517gustar00rootroot0000000000000052 comment=f3063092f09b3bf4d93c6f7f702daa9084123e45 pysimplesoap-1.16/000077500000000000000000000000001245630617100141735ustar00rootroot00000000000000pysimplesoap-1.16/.gitignore000066400000000000000000000001401245630617100161560ustar00rootroot00000000000000*.pyc issue*.xml /.idea /PySimpleSOAP.egg-info /.coverage lib/* bin/* include/* build/ dist/ pysimplesoap-1.16/.hgignore000066400000000000000000000000511245630617100157720ustar00rootroot00000000000000syntax: glob *.pyc lib/* bin/* include/* pysimplesoap-1.16/.hgtags000066400000000000000000000006101245630617100154460ustar00rootroot000000000000001b656e9df5e25b15aa72326f581f168696b3ec46 1.10 57bca8475baba500b23bdae40f15e3be644ef698 update 0000000000000000000000000000000000000000 update 57bca8475baba500b23bdae40f15e3be644ef698 f289e16a0972 0000000000000000000000000000000000000000 f289e16a0972 f289e16a0972be7d3a64901d713778381c919c2d 1.11-git 1a76eb59cdf5921344c9ec667af7b25ff8774e46 1.12 2ba439cbd4fc525bc2c62c5fe7b3bd9a83ad6336 1.13 pysimplesoap-1.16/.travis.yml000077500000000000000000000001651245630617100163110ustar00rootroot00000000000000language: python python: - "2.6" - "2.7" - "3.2" - "3.3" - "pypy" script: nosetests -a !internal,!disabled pysimplesoap-1.16/AUTHORS.md000066400000000000000000000013051245630617100156410ustar00rootroot00000000000000Maintainer ======== - Mariano Reingart Contributors ======== - Dean Gardiner @fuzeman - Piotr Staroszczyk @oczkers - Rui Carmo @rcarmo Patches and Suggestions ======== - Marcelo Alaniz - Margarita Manterola - Gerardo Allende - Darell Tan - Alan Etkin - Richard Jones - matee - Yuan Liu - Pat Kujawa - Alfredo Saglimbeni @asaglimbeni - Jor S - Akram Parvez - beppler - Remco Boerma - Sergei Lemeshkin - Tim Alexander - Ralf Henschkowski - Allan Crooks - Paul Jimenez - Jonathan Balsano - Kevin Bullmann - Pedro Hdez - jpc - Luka Birsa - Bill Bennert @billwebreply pysimplesoap-1.16/README.md000066400000000000000000000013051245630617100154510ustar00rootroot00000000000000Features ======== This fork has the following features: * Corrected support for multiple SOAP ports/bindings * Support for both `import` and `include` stanzas in WSDL * Support for a WSDL base directory to deal with relative pathnames in import/include stanzas * Somewhat saner traceing/logging (traces now go to log.debug(), which you can handle per module) * Somewhat more readable logic (by removing a bunch of helpers to a separate file) Testing ======= Using Python 2.7+: python -m unittest discover Using older Python versions: python -m unittest tests/suite.py Code coverage: sudo pip install coverage coverage run tests/suite.py coverage report -m coverage html pysimplesoap-1.16/pysimplesoap/000077500000000000000000000000001245630617100167205ustar00rootroot00000000000000pysimplesoap-1.16/pysimplesoap/__init__.py000066400000000000000000000004631245630617100210340ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- """PySimpleSOAP""" __author__ = "Mariano Reingart" __author_email__ = "reingart@gmail.com" __copyright__ = "Copyright (C) 2013 Mariano Reingart" __license__ = "LGPL 3.0" __version__ = "1.16" TIMEOUT = 60 from . import client, server, simplexml, transport pysimplesoap-1.16/pysimplesoap/c14n.py000066400000000000000000000401131245630617100200360ustar00rootroot00000000000000#! /usr/bin/env python '''XML Canonicalization Patches Applied to xml.dom.ext.c14n: http://sourceforge.net/projects/pyxml/ [ 1444526 ] c14n.py: http://www.w3.org/TR/xml-exc-c14n/ fix -- includes [ 829905 ] c14n.py fix for bug #825115, Date Submitted: 2003-10-24 23:43 -- include dependent namespace declarations declared in ancestor nodes (checking attributes and tags), -- handle InclusiveNamespaces PrefixList parameter This module generates canonical XML of a document or element. http://www.w3.org/TR/2001/REC-xml-c14n-20010315 and includes a prototype of exclusive canonicalization http://www.w3.org/Signature/Drafts/xml-exc-c14n Requires PyXML 0.7.0 or later. Known issues if using Ft.Lib.pDomlette: 1. Unicode 2. does not white space normalize attributes of type NMTOKEN and ID? 3. seems to be include "\n" after importing external entities? Note, this version processes a DOM tree, and consequently it processes namespace nodes as attributes, not from a node's namespace axis. This permits simple document and element canonicalization without XPath. When XPath is used, the XPath result node list is passed and used to determine if the node is in the XPath result list, but little else. Authors: "Joseph M. Reagle Jr." "Rich Salz" $Date: 2006-03-30 23:47:16 +0000 (Thu, 30 Mar 2006) $ by $Author: boverhof $ ''' _copyright = '''Copyright 2001, Zolera Systems Inc. All Rights Reserved. Copyright 2001, MIT. All Rights Reserved. Distributed under the terms of: Python 2.0 License or later. http://www.python.org/2.0.1/license.html or W3C Software License http://www.w3.org/Consortium/Legal/copyright-software-19980720 ''' import string from xml.dom import Node try: from xml.ns import XMLNS except: class XMLNS: BASE = "http://www.w3.org/2000/xmlns/" XML = "http://www.w3.org/XML/1998/namespace" try: import cStringIO StringIO = cStringIO except ImportError: import StringIO _attrs = lambda E: (E.attributes and E.attributes.values()) or [] _children = lambda E: E.childNodes or [] _IN_XML_NS = lambda n: n.name.startswith("xmlns") _inclusive = lambda n: n.unsuppressedPrefixes == None # Does a document/PI has lesser/greater document order than the # first element? _LesserElement, _Element, _GreaterElement = range(3) def _sorter(n1,n2): '''_sorter(n1,n2) -> int Sorting predicate for non-NS attributes.''' i = cmp(n1.namespaceURI, n2.namespaceURI) if i: return i return cmp(n1.localName, n2.localName) def _sorter_ns(n1,n2): '''_sorter_ns((n,v),(n,v)) -> int "(an empty namespace URI is lexicographically least)."''' if n1[0] == 'xmlns': return -1 if n2[0] == 'xmlns': return 1 return cmp(n1[0], n2[0]) def _utilized(n, node, other_attrs, unsuppressedPrefixes): '''_utilized(n, node, other_attrs, unsuppressedPrefixes) -> boolean Return true if that nodespace is utilized within the node''' if n.startswith('xmlns:'): n = n[6:] elif n.startswith('xmlns'): n = n[5:] if (n=="" and node.prefix in ["#default", None]) or \ n == node.prefix or n in unsuppressedPrefixes: return 1 for attr in other_attrs: if n == attr.prefix: return 1 # For exclusive need to look at attributes if unsuppressedPrefixes is not None: for attr in _attrs(node): if n == attr.prefix: return 1 return 0 def _inclusiveNamespacePrefixes(node, context, unsuppressedPrefixes): '''http://www.w3.org/TR/xml-exc-c14n/ InclusiveNamespaces PrefixList parameter, which lists namespace prefixes that are handled in the manner described by the Canonical XML Recommendation''' inclusive = [] if node.prefix: usedPrefixes = ['xmlns:%s' %node.prefix] else: usedPrefixes = ['xmlns'] for a in _attrs(node): if a.nodeName.startswith('xmlns') or not a.prefix: continue usedPrefixes.append('xmlns:%s' %a.prefix) unused_namespace_dict = {} for attr in context: n = attr.nodeName if n in unsuppressedPrefixes: inclusive.append(attr) elif n.startswith('xmlns:') and n[6:] in unsuppressedPrefixes: inclusive.append(attr) elif n.startswith('xmlns') and n[5:] in unsuppressedPrefixes: inclusive.append(attr) elif attr.nodeName in usedPrefixes: inclusive.append(attr) elif n.startswith('xmlns:'): unused_namespace_dict[n] = attr.value return inclusive, unused_namespace_dict #_in_subset = lambda subset, node: not subset or node in subset _in_subset = lambda subset, node: subset is None or node in subset # rich's tweak class _implementation: '''Implementation class for C14N. This accompanies a node during it's processing and includes the parameters and processing state.''' # Handler for each node type; populated during module instantiation. handlers = {} def __init__(self, node, write, **kw): '''Create and run the implementation.''' self.write = write self.subset = kw.get('subset') self.comments = kw.get('comments', 0) self.unsuppressedPrefixes = kw.get('unsuppressedPrefixes') nsdict = kw.get('nsdict', { 'xml': XMLNS.XML, 'xmlns': XMLNS.BASE }) # Processing state. self.state = (nsdict, {'xml':''}, {}, {}) #0422 if node.nodeType == Node.DOCUMENT_NODE: self._do_document(node) elif node.nodeType == Node.ELEMENT_NODE: self.documentOrder = _Element # At document element if not _inclusive(self): inherited,unused = _inclusiveNamespacePrefixes(node, self._inherit_context(node), self.unsuppressedPrefixes) self._do_element(node, inherited, unused=unused) else: inherited = self._inherit_context(node) self._do_element(node, inherited) elif node.nodeType == Node.DOCUMENT_TYPE_NODE: pass else: raise TypeError, str(node) def _inherit_context(self, node): '''_inherit_context(self, node) -> list Scan ancestors of attribute and namespace context. Used only for single element node canonicalization, not for subset canonicalization.''' # Collect the initial list of xml:foo attributes. xmlattrs = filter(_IN_XML_NS, _attrs(node)) # Walk up and get all xml:XXX attributes we inherit. inherited, parent = [], node.parentNode while parent and parent.nodeType == Node.ELEMENT_NODE: for a in filter(_IN_XML_NS, _attrs(parent)): n = a.localName if n not in xmlattrs: xmlattrs.append(n) inherited.append(a) parent = parent.parentNode return inherited def _do_document(self, node): '''_do_document(self, node) -> None Process a document node. documentOrder holds whether the document element has been encountered such that PIs/comments can be written as specified.''' self.documentOrder = _LesserElement for child in node.childNodes: if child.nodeType == Node.ELEMENT_NODE: self.documentOrder = _Element # At document element self._do_element(child) self.documentOrder = _GreaterElement # After document element elif child.nodeType == Node.PROCESSING_INSTRUCTION_NODE: self._do_pi(child) elif child.nodeType == Node.COMMENT_NODE: self._do_comment(child) elif child.nodeType == Node.DOCUMENT_TYPE_NODE: pass else: raise TypeError, str(child) handlers[Node.DOCUMENT_NODE] = _do_document def _do_text(self, node): '''_do_text(self, node) -> None Process a text or CDATA node. Render various special characters as their C14N entity representations.''' if not _in_subset(self.subset, node): return s = string.replace(node.data, "&", "&") s = string.replace(s, "<", "<") s = string.replace(s, ">", ">") s = string.replace(s, "\015", " ") if s: self.write(s) handlers[Node.TEXT_NODE] = _do_text handlers[Node.CDATA_SECTION_NODE] = _do_text def _do_pi(self, node): '''_do_pi(self, node) -> None Process a PI node. Render a leading or trailing #xA if the document order of the PI is greater or lesser (respectively) than the document element. ''' if not _in_subset(self.subset, node): return W = self.write if self.documentOrder == _GreaterElement: W('\n') W('') if self.documentOrder == _LesserElement: W('\n') handlers[Node.PROCESSING_INSTRUCTION_NODE] = _do_pi def _do_comment(self, node): '''_do_comment(self, node) -> None Process a comment node. Render a leading or trailing #xA if the document order of the comment is greater or lesser (respectively) than the document element. ''' if not _in_subset(self.subset, node): return if self.comments: W = self.write if self.documentOrder == _GreaterElement: W('\n') W('') if self.documentOrder == _LesserElement: W('\n') handlers[Node.COMMENT_NODE] = _do_comment def _do_attr(self, n, value): ''''_do_attr(self, node) -> None Process an attribute.''' W = self.write W(' ') W(n) W('="') s = string.replace(value, "&", "&") s = string.replace(s, "<", "<") s = string.replace(s, '"', '"') s = string.replace(s, '\011', ' ') s = string.replace(s, '\012', ' ') s = string.replace(s, '\015', ' ') W(s) W('"') def _do_element(self, node, initial_other_attrs = [], unused = None): '''_do_element(self, node, initial_other_attrs = [], unused = {}) -> None Process an element (and its children).''' # Get state (from the stack) make local copies. # ns_parent -- NS declarations in parent # ns_rendered -- NS nodes rendered by ancestors # ns_local -- NS declarations relevant to this element # xml_attrs -- Attributes in XML namespace from parent # xml_attrs_local -- Local attributes in XML namespace. # ns_unused_inherited -- not rendered namespaces, used for exclusive ns_parent, ns_rendered, xml_attrs = \ self.state[0], self.state[1].copy(), self.state[2].copy() #0422 ns_unused_inherited = unused if unused is None: ns_unused_inherited = self.state[3].copy() ns_local = ns_parent.copy() inclusive = _inclusive(self) xml_attrs_local = {} # Divide attributes into NS, XML, and others. other_attrs = [] in_subset = _in_subset(self.subset, node) for a in initial_other_attrs + _attrs(node): if a.namespaceURI == XMLNS.BASE: n = a.nodeName if n == "xmlns:": n = "xmlns" # DOM bug workaround ns_local[n] = a.nodeValue elif a.namespaceURI == XMLNS.XML: if inclusive or (in_subset and _in_subset(self.subset, a)): #020925 Test to see if attribute node in subset xml_attrs_local[a.nodeName] = a #0426 else: if _in_subset(self.subset, a): #020925 Test to see if attribute node in subset other_attrs.append(a) # # TODO: exclusive, might need to define xmlns:prefix here # if not inclusive and a.prefix is not None and not ns_rendered.has_key('xmlns:%s' %a.prefix): # ns_local['xmlns:%s' %a.prefix] = ?? #add local xml:foo attributes to ancestor's xml:foo attributes xml_attrs.update(xml_attrs_local) # Render the node W, name = self.write, None if in_subset: name = node.nodeName if not inclusive: if node.prefix is not None: prefix = 'xmlns:%s' %node.prefix else: prefix = 'xmlns' if not ns_rendered.has_key(prefix) and not ns_local.has_key(prefix): if not ns_unused_inherited.has_key(prefix): raise RuntimeError,\ 'For exclusive c14n, unable to map prefix "%s" in %s' %( prefix, node) ns_local[prefix] = ns_unused_inherited[prefix] del ns_unused_inherited[prefix] W('<') W(name) # Create list of NS attributes to render. ns_to_render = [] for n,v in ns_local.items(): # If default namespace is XMLNS.BASE or empty, # and if an ancestor was the same if n == "xmlns" and v in [ XMLNS.BASE, '' ] \ and ns_rendered.get('xmlns') in [ XMLNS.BASE, '', None ]: continue # "omit namespace node with local name xml, which defines # the xml prefix, if its string value is # http://www.w3.org/XML/1998/namespace." if n in ["xmlns:xml", "xml"] \ and v in [ 'http://www.w3.org/XML/1998/namespace' ]: continue # If not previously rendered # and it's inclusive or utilized if (n,v) not in ns_rendered.items(): if inclusive or _utilized(n, node, other_attrs, self.unsuppressedPrefixes): ns_to_render.append((n, v)) elif not inclusive: ns_unused_inherited[n] = v # Sort and render the ns, marking what was rendered. ns_to_render.sort(_sorter_ns) for n,v in ns_to_render: self._do_attr(n, v) ns_rendered[n]=v #0417 # If exclusive or the parent is in the subset, add the local xml attributes # Else, add all local and ancestor xml attributes # Sort and render the attributes. if not inclusive or _in_subset(self.subset,node.parentNode): #0426 other_attrs.extend(xml_attrs_local.values()) else: other_attrs.extend(xml_attrs.values()) other_attrs.sort(_sorter) for a in other_attrs: self._do_attr(a.nodeName, a.value) W('>') # Push state, recurse, pop state. state, self.state = self.state, (ns_local, ns_rendered, xml_attrs, ns_unused_inherited) for c in _children(node): _implementation.handlers[c.nodeType](self, c) self.state = state if name: W('' % name) handlers[Node.ELEMENT_NODE] = _do_element def Canonicalize(node, output=None, **kw): '''Canonicalize(node, output=None, **kw) -> UTF-8 Canonicalize a DOM document/element node and all descendents. Return the text; if output is specified then output.write will be called to output the text and None will be returned Keyword parameters: nsdict: a dictionary of prefix:uri namespace entries assumed to exist in the surrounding context comments: keep comments if non-zero (default is 0) subset: Canonical XML subsetting resulting from XPath (default is []) unsuppressedPrefixes: do exclusive C14N, and this specifies the prefixes that should be inherited. ''' if output: apply(_implementation, (node, output.write), kw) else: s = StringIO.StringIO() apply(_implementation, (node, s.write), kw) return s.getvalue() pysimplesoap-1.16/pysimplesoap/client.py000066400000000000000000001142271245630617100205570ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Pythonic simple SOAP Client implementation""" from __future__ import unicode_literals import sys if sys.version > '3': unicode = str try: import cPickle as pickle except ImportError: import pickle import copy import hashlib import logging import os import tempfile import warnings from . import __author__, __copyright__, __license__, __version__, TIMEOUT from .simplexml import SimpleXMLElement, TYPE_MAP, REVERSE_TYPE_MAP, Struct from .transport import get_http_wrapper, set_http_wrapper, get_Http # Utility functions used throughout wsdl_parse, moved aside for readability from .helpers import fetch, sort_dict, make_key, process_element, \ postprocess_element, get_message, preprocess_schema, \ get_local_name, get_namespace_prefix, TYPE_MAP, urlsplit from .wsse import UsernameToken log = logging.getLogger(__name__) class SoapFault(RuntimeError): def __init__(self, faultcode, faultstring, detail=None): self.faultcode = faultcode self.faultstring = faultstring self.detail = detail RuntimeError.__init__(self, faultcode, faultstring, detail) def __unicode__(self): return '%s: %s' % (self.faultcode, self.faultstring) if sys.version > '3': __str__ = __unicode__ else: def __str__(self): return self.__unicode__().encode('ascii', 'ignore') def __repr__(self): return "SoapFault(faultcode = %s, faultstring %s, detail = %s)" % (repr(self.faultcode), repr(self.faultstring), repr(self.detail)) # soap protocol specification & namespace soap_namespaces = dict( soap11='http://schemas.xmlsoap.org/soap/envelope/', soap='http://schemas.xmlsoap.org/soap/envelope/', soapenv='http://schemas.xmlsoap.org/soap/envelope/', soap12='http://www.w3.org/2003/05/soap-env', soap12env="http://www.w3.org/2003/05/soap-envelope", ) class SoapClient(object): """Simple SOAP Client (simil PHP)""" def __init__(self, location=None, action=None, namespace=None, cert=None, exceptions=True, proxy=None, ns=None, soap_ns=None, wsdl=None, wsdl_basedir='', cache=False, cacert=None, sessions=False, soap_server=None, timeout=TIMEOUT, http_headers=None, trace=False, username=None, password=None, key_file=None, plugins=None, ): """ :param http_headers: Additional HTTP Headers; example: {'Host': 'ipsec.example.com'} """ self.certssl = cert self.keyssl = key_file self.location = location # server location (url) self.action = action # SOAP base action self.namespace = namespace # message self.exceptions = exceptions # lanzar execpiones? (Soap Faults) self.xml_request = self.xml_response = '' self.http_headers = http_headers or {} self.plugins = plugins or [] # extract the base directory / url for wsdl relative imports: if wsdl and wsdl_basedir == '': # parse the wsdl url, strip the scheme and filename url_scheme, netloc, path, query, fragment = urlsplit(wsdl) wsdl_basedir = os.path.dirname(netloc + path) self.wsdl_basedir = wsdl_basedir # shortcut to print all debugging info and sent / received xml messages if trace: if trace is True: level = logging.DEBUG # default logging level else: level = trace # use the provided level logging.basicConfig(level=level) log.setLevel(level) if not soap_ns and not ns: self.__soap_ns = 'soap' # 1.1 elif not soap_ns and ns: self.__soap_ns = 'soapenv' # 1.2 else: self.__soap_ns = soap_ns # SOAP Server (special cases like oracle, jbossas6 or jetty) self.__soap_server = soap_server # SOAP Header support self.__headers = {} # general headers self.__call_headers = None # Struct to be marshalled for RPC Call # check if the Certification Authority Cert is a string and store it if cacert and cacert.startswith('-----BEGIN CERTIFICATE-----'): fd, filename = tempfile.mkstemp() f = os.fdopen(fd, 'w+b', -1) log.debug("Saving CA certificate to %s" % filename) f.write(cacert) cacert = filename f.close() self.cacert = cacert # Create HTTP wrapper Http = get_Http() self.http = Http(timeout=timeout, cacert=cacert, proxy=proxy, sessions=sessions) if username and password: if hasattr(self.http, 'add_credentials'): self.http.add_credentials(username, password) if cert and key_file: if hasattr(self.http, 'add_certificate'): self.http.add_certificate(key=key_file, cert=cert, domain='') # namespace prefix, None to use xmlns attribute or False to not use it: self.__ns = ns if not ns: self.__xml = """ <%(soap_ns)s:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:%(soap_ns)s="%(soap_uri)s"> <%(soap_ns)s:Header/> <%(soap_ns)s:Body> <%(method)s xmlns="%(namespace)s"> """ else: self.__xml = """ <%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" xmlns:%(ns)s="%(namespace)s"> <%(soap_ns)s:Header/> <%(soap_ns)s:Body> <%(ns)s:%(method)s> """ # parse wsdl url self.services = wsdl and self.wsdl_parse(wsdl, cache=cache) self.service_port = None # service port for late binding def __getattr__(self, attr): """Return a pseudo-method that can be called""" if not self.services: # not using WSDL? return lambda self=self, *args, **kwargs: self.call(attr, *args, **kwargs) else: # using WSDL: return lambda *args, **kwargs: self.wsdl_call(attr, *args, **kwargs) def call(self, method, *args, **kwargs): """Prepare xml request and make SOAP call, returning a SimpleXMLElement. If a keyword argument called "headers" is passed with a value of a SimpleXMLElement object, then these headers will be inserted into the request. """ #TODO: method != input_message # Basic SOAP request: soap_uri = soap_namespaces[self.__soap_ns] xml = self.__xml % dict(method=method, # method tag name namespace=self.namespace, # method ns uri ns=self.__ns, # method ns prefix soap_ns=self.__soap_ns, # soap prefix & uri soap_uri=soap_uri) request = SimpleXMLElement(xml, namespace=self.__ns and self.namespace, prefix=self.__ns) request_headers = kwargs.pop('headers', None) # serialize parameters if kwargs: parameters = list(kwargs.items()) else: parameters = args if parameters and isinstance(parameters[0], SimpleXMLElement): body = request('Body', ns=list(soap_namespaces.values()),) # remove default body parameter (method name) delattr(body, method) # merge xmlelement parameter ("raw" - already marshalled) body.import_node(parameters[0]) elif parameters: # marshall parameters: use_ns = None if (self.__soap_server == "jetty" or self.qualified is False) else True for k, v in parameters: # dict: tag=valor if hasattr(v, "namespaces") and use_ns: ns = v.namespaces.get(None, True) else: ns = use_ns getattr(request, method).marshall(k, v, ns=ns) elif self.__soap_server in ('jbossas6',): # JBossAS-6 requires no empty method parameters! delattr(request("Body", ns=list(soap_namespaces.values()),), method) # construct header and parameters (if not wsdl given) except wsse if self.__headers and not self.services: self.__call_headers = dict([(k, v) for k, v in self.__headers.items() if not k.startswith('wsse:')]) # always extract WS Security header and send it (backward compatible) if 'wsse:Security' in self.__headers and not self.plugins: warnings.warn("Replace wsse:Security with UsernameToken plugin", DeprecationWarning) self.plugins.append(UsernameToken()) if self.__call_headers: header = request('Header', ns=list(soap_namespaces.values()),) for k, v in self.__call_headers.items(): ##if not self.__ns: ## header['xmlns'] if isinstance(v, SimpleXMLElement): # allows a SimpleXMLElement to be constructed and inserted # rather than a dictionary. marshall doesn't allow ns: prefixes # in dict key names header.import_node(v) else: header.marshall(k, v, ns=self.__ns, add_children_ns=False) if request_headers: header = request('Header', ns=list(soap_namespaces.values()),) for subheader in request_headers.children(): header.import_node(subheader) # do pre-processing using plugins (i.e. WSSE signing) for plugin in self.plugins: plugin.preprocess(self, request, method, args, kwargs, self.__headers, soap_uri) self.xml_request = request.as_xml() self.xml_response = self.send(method, self.xml_request) response = SimpleXMLElement(self.xml_response, namespace=self.namespace, jetty=self.__soap_server in ('jetty',)) if self.exceptions and response("Fault", ns=list(soap_namespaces.values()), error=False): detailXml = response("detail", ns=list(soap_namespaces.values()), error=False) detail = None if detailXml and detailXml.children(): operation = self.get_operation(method) fault = operation['faults'][detailXml.children()[0].get_name()] detail = detailXml.children()[0].unmarshall(fault, strict=False) raise SoapFault(unicode(response.faultcode), unicode(response.faultstring), detail) # do post-processing using plugins (i.e. WSSE signature verification) for plugin in self.plugins: plugin.postprocess(self, response, method, args, kwargs, self.__headers, soap_uri) return response def send(self, method, xml): """Send SOAP request using HTTP""" if self.location == 'test': return # location = '%s' % self.location #?op=%s" % (self.location, method) http_method = str('POST') location = str(self.location) if self.services: soap_action = str(self.action) else: soap_action = str(self.action) + method headers = { 'Content-type': 'text/xml; charset="UTF-8"', 'Content-length': str(len(xml)), 'SOAPAction': '"%s"' % soap_action } headers.update(self.http_headers) log.info("POST %s" % location) log.debug('\n'.join(["%s: %s" % (k, v) for k, v in headers.items()])) log.debug(xml) if sys.version < '3': # Ensure http_method, location and all headers are binary to prevent # UnicodeError inside httplib.HTTPConnection._send_output. # httplib in python3 do the same inside itself, don't need to convert it here headers = dict((str(k), str(v)) for k, v in headers.items()) response, content = self.http.request( location, http_method, body=xml, headers=headers) self.response = response self.content = content log.debug('\n'.join(["%s: %s" % (k, v) for k, v in response.items()])) log.debug(content) return content def get_operation(self, method): # try to find operation in wsdl file soap_ver = self.__soap_ns.startswith('soap12') and 'soap12' or 'soap11' if not self.service_port: for service_name, service in self.services.items(): for port_name, port in [port for port in service['ports'].items()]: if port['soap_ver'] == soap_ver: self.service_port = service_name, port_name break else: raise RuntimeError('Cannot determine service in WSDL: ' 'SOAP version: %s' % soap_ver) else: port = self.services[self.service_port[0]]['ports'][self.service_port[1]] if not self.location: self.location = port['location'] operation = port['operations'].get(method) if not operation: raise RuntimeError('Operation %s not found in WSDL: ' 'Service/Port Type: %s' % (method, self.service_port)) return operation def wsdl_call(self, method, *args, **kwargs): """Pre and post process SOAP call, input and output parameters using WSDL""" return self.wsdl_call_with_args(method, args, kwargs) def wsdl_call_with_args(self, method, args, kwargs): """Pre and post process SOAP call, input and output parameters using WSDL""" soap_uri = soap_namespaces[self.__soap_ns] operation = self.get_operation(method) # get i/o type declarations: input = operation['input'] output = operation['output'] header = operation.get('header') if 'action' in operation: self.action = operation['action'] if 'namespace' in operation: self.namespace = operation['namespace'] or '' self.qualified = operation['qualified'] # construct header and parameters if header: self.__call_headers = sort_dict(header, self.__headers) method, params = self.wsdl_call_get_params(method, input, args, kwargs) # call remote procedure response = self.call(method, *params) # parse results: resp = response('Body', ns=soap_uri).children().unmarshall(output) return resp and list(resp.values())[0] # pass Response tag children def wsdl_call_get_params(self, method, input, args, kwargs): """Build params from input and args/kwargs""" params = inputname = inputargs = None all_args = {} if input: inputname = list(input.keys())[0] inputargs = input[inputname] if input and args: # convert positional parameters to named parameters: d = {} for idx, arg in enumerate(args): key = list(inputargs.keys())[idx] if isinstance(arg, dict): if key not in arg: raise KeyError('Unhandled key %s. use client.help(method)' % key) d[key] = arg[key] else: d[key] = arg all_args.update({inputname: d}) if input and (kwargs or all_args): if kwargs: all_args.update({inputname: kwargs}) valid, errors, warnings = self.wsdl_validate_params(input, all_args) if not valid: raise ValueError('Invalid Args Structure. Errors: %s' % errors) # sort and filter parameters acording wsdl input structure tree = sort_dict(input, all_args) root = list(tree.values())[0] params = [] # make a params tuple list suitable for self.call(method, *params) for k, v in root.items(): # fix referenced namespaces as info is lost when calling call root_ns = root.namespaces[k] if not root.references[k] and isinstance(v, Struct): v.namespaces[None] = root_ns params.append((k, v)) # TODO: check style and document attributes if self.__soap_server in ('axis', ): # use the operation name method = method else: # use the message (element) name method = inputname #elif not input: #TODO: no message! (see wsmtxca.dummy) else: params = kwargs and kwargs.items() return (method, params) def wsdl_validate_params(self, struct, value): """Validate the arguments (actual values) for the parameters structure. Fail for any invalid arguments or type mismatches.""" errors = [] warnings = [] valid = True # Determine parameter type if type(struct) == type(value): typematch = True if not isinstance(struct, dict) and isinstance(value, dict): typematch = True # struct can be a dict or derived (Struct) else: typematch = False if struct == str: struct = unicode # fix for py2 vs py3 string handling if not isinstance(struct, (list, dict, tuple)) and struct in TYPE_MAP.keys(): if not type(value) == struct and value is not None: try: struct(value) # attempt to cast input to parameter type except: valid = False errors.append('Type mismatch for argument value. parameter(%s): %s, value(%s): %s' % (type(struct), struct, type(value), value)) elif isinstance(struct, list) and len(struct) == 1 and not isinstance(value, list): # parameter can have a dict in a list: [{}] indicating a list is allowed, but not needed if only one argument. next_valid, next_errors, next_warnings = self.wsdl_validate_params(struct[0], value) if not next_valid: valid = False errors.extend(next_errors) warnings.extend(next_warnings) # traverse tree elif isinstance(struct, dict): if struct and value: for key in value: if key not in struct: valid = False errors.append('Argument key %s not in parameter. parameter: %s, args: %s' % (key, struct, value)) else: next_valid, next_errors, next_warnings = self.wsdl_validate_params(struct[key], value[key]) if not next_valid: valid = False errors.extend(next_errors) warnings.extend(next_warnings) for key in struct: if key not in value: warnings.append('Parameter key %s not in args. parameter: %s, value: %s' % (key, struct, value)) elif struct and not value: warnings.append('parameter keys not in args. parameter: %s, args: %s' % (struct, value)) elif not struct and value: valid = False errors.append('Args keys not in parameter. parameter: %s, args: %s' % (struct, value)) else: pass elif isinstance(struct, list): struct_list_value = struct[0] for item in value: next_valid, next_errors, next_warnings = self.wsdl_validate_params(struct_list_value, item) if not next_valid: valid = False errors.extend(next_errors) warnings.extend(next_warnings) elif not typematch: valid = False errors.append('Type mismatch. parameter(%s): %s, value(%s): %s' % (type(struct), struct, type(value), value)) return (valid, errors, warnings) def help(self, method): """Return operation documentation and invocation/returned value example""" operation = self.get_operation(method) input = operation.get('input') input = input and input.values() and list(input.values())[0] if isinstance(input, dict): input = ", ".join("%s=%s" % (k, repr(v)) for k, v in input.items()) elif isinstance(input, list): input = repr(input) output = operation.get('output') if output: output = list(operation['output'].values())[0] headers = operation.get('headers') or None return "%s(%s)\n -> %s:\n\n%s\nHeaders: %s" % ( method, input or '', output and output or '', operation.get('documentation', ''), headers, ) soap_ns_uris = { 'http://schemas.xmlsoap.org/wsdl/soap/': 'soap11', 'http://schemas.xmlsoap.org/wsdl/soap12/': 'soap12', } wsdl_uri = 'http://schemas.xmlsoap.org/wsdl/' xsd_uri = 'http://www.w3.org/2001/XMLSchema' xsi_uri = 'http://www.w3.org/2001/XMLSchema-instance' def _url_to_xml_tree(self, url, cache, force_download): # Open uri and read xml: xml = fetch(url, self.http, cache, force_download, self.wsdl_basedir, self.http_headers) # Parse WSDL XML: wsdl = SimpleXMLElement(xml, namespace=self.wsdl_uri) # Extract useful data: self.namespace = "" self.documentation = unicode(wsdl('documentation', error=False)) or '' # some wsdl are splitted down in several files, join them: imported_wsdls = {} for element in wsdl.children() or []: if element.get_local_name() in ('import'): wsdl_namespace = element['namespace'] wsdl_location = element['location'] if wsdl_location is None: log.warning('WSDL location not provided for %s!' % wsdl_namespace) continue if wsdl_location in imported_wsdls: log.warning('WSDL %s already imported!' % wsdl_location) continue imported_wsdls[wsdl_location] = wsdl_namespace log.debug('Importing wsdl %s from %s' % (wsdl_namespace, wsdl_location)) # Open uri and read xml: xml = fetch(wsdl_location, self.http, cache, force_download, self.wsdl_basedir, self.http_headers) # Parse imported XML schema (recursively): imported_wsdl = SimpleXMLElement(xml, namespace=self.xsd_uri) # merge the imported wsdl into the main document: wsdl.import_node(imported_wsdl) # warning: do not process schemas to avoid infinite recursion! return wsdl def _xml_tree_to_services(self, wsdl, cache, force_download): # detect soap prefix and uri (xmlns attributes of ) xsd_ns = None soap_uris = {} for k, v in wsdl[:]: if v in self.soap_ns_uris and k.startswith('xmlns:'): soap_uris[get_local_name(k)] = v if v == self.xsd_uri and k.startswith('xmlns:'): xsd_ns = get_local_name(k) elements = {} # element: type def messages = {} # message: element port_types = {} # port_type_name: port_type bindings = {} # binding_name: binding services = {} # service_name: service # check axis2 namespace at schema types attributes (europa.eu checkVat) if "http://xml.apache.org/xml-soap" in dict(wsdl[:]).values(): # get the sub-namespace in the first schema element (see issue 8) if wsdl('types', error=False): schema = wsdl.types('schema', ns=self.xsd_uri) attrs = dict(schema[:]) self.namespace = attrs.get('targetNamespace', self.namespace) if not self.namespace or self.namespace == "urn:DefaultNamespace": self.namespace = wsdl['targetNamespace'] or self.namespace imported_schemas = {} global_namespaces = {None: self.namespace} # process current wsdl schema (if any, or many if imported): for types in wsdl('types', error=False) or []: # avoid issue if schema is not given in the main WSDL file schemas = types('schema', ns=self.xsd_uri, error=False) for schema in schemas or []: preprocess_schema(schema, imported_schemas, elements, self.xsd_uri, self.__soap_server, self.http, cache, force_download, self.wsdl_basedir, global_namespaces=global_namespaces) # 2nd phase: alias, postdefined elements, extend bases, convert lists postprocess_element(elements, []) for message in wsdl.message: for part in message('part', error=False) or []: element = {} element_name = part['element'] if not element_name: # some implementations (axis) uses type instead element_name = part['type'] type_ns = get_namespace_prefix(element_name) type_uri = part.get_namespace_uri(type_ns) part_name = part['name'] or None if type_uri == self.xsd_uri: element_name = get_local_name(element_name) fn = REVERSE_TYPE_MAP.get(element_name, None) element = {part_name: fn} # emulate a true Element (complexType) for rpc style if (message['name'], part_name) not in messages: od = Struct() od.namespaces[None] = type_uri messages[(message['name'], part_name)] = {message['name']: od} else: od = messages[(message['name'], part_name)].values()[0] od.namespaces[part_name] = type_uri od.references[part_name] = False od.update(element) else: element_name = get_local_name(element_name) fn = elements.get(make_key(element_name, 'element', type_uri)) if not fn: # some axis servers uses complexType for part messages (rpc) fn = elements.get(make_key(element_name, 'complexType', type_uri)) od = Struct() od[part_name] = fn od.namespaces[None] = type_uri od.namespaces[part_name] = type_uri od.references[part_name] = False element = {message['name']: od} else: element = {element_name: fn} messages[(message['name'], part_name)] = element for port_type_node in wsdl.portType: port_type_name = port_type_node['name'] port_type = port_types[port_type_name] = {} operations = port_type['operations'] = {} for operation_node in port_type_node.operation: op_name = operation_node['name'] op = operations[op_name] = {} op['style'] = operation_node['style'] op['parameter_order'] = (operation_node['parameterOrder'] or "").split(" ") op['documentation'] = unicode(operation_node('documentation', error=False)) or '' if operation_node('input', error=False): op['input_msg'] = get_local_name(operation_node.input['message']) ns = get_namespace_prefix(operation_node.input['message']) op['namespace'] = operation_node.get_namespace_uri(ns) if operation_node('output', error=False): op['output_msg'] = get_local_name(operation_node.output['message']) #Get all fault message types this operation may return fault_msgs = op['fault_msgs'] = {} faults = operation_node('fault', error=False) if faults is not None: for fault in operation_node('fault', error=False): fault_msgs[fault['name']] = get_local_name(fault['message']) for binding_node in wsdl.binding: port_type_name = get_local_name(binding_node['type']) if port_type_name not in port_types: # Invalid port type continue port_type = port_types[port_type_name] binding_name = binding_node['name'] soap_binding = binding_node('binding', ns=list(soap_uris.values()), error=False) transport = soap_binding and soap_binding['transport'] or None style = soap_binding and soap_binding['style'] or None # rpc binding = bindings[binding_name] = { 'name': binding_name, 'operations': copy.deepcopy(port_type['operations']), 'port_type_name': port_type_name, 'transport': transport, 'style': style, } for operation_node in binding_node.operation: op_name = operation_node['name'] op_op = operation_node('operation', ns=list(soap_uris.values()), error=False) action = op_op and op_op['soapAction'] op = binding['operations'].setdefault(op_name, {}) op['name'] = op_name op['style'] = op.get('style', style) if action: op['action'] = action # input and/or ouput can be not present! input = operation_node('input', error=False) body = input and input('body', ns=list(soap_uris.values()), error=False) parts_input_body = body and body['parts'] or None # parse optional header messages (some implementations use more than one!) parts_input_headers = [] headers = input and input('header', ns=list(soap_uris.values()), error=False) for header in headers or []: hdr = {'message': header['message'], 'part': header['part']} parts_input_headers.append(hdr) if 'input_msg' in op: headers = {} # base header message structure for input_header in parts_input_headers: header_msg = get_local_name(input_header.get('message')) header_part = get_local_name(input_header.get('part')) # warning: some implementations use a separate message! hdr = get_message(messages, header_msg or op['input_msg'], header_part) if hdr: headers.update(hdr) else: pass # not enought info to search the header message: op['input'] = get_message(messages, op['input_msg'], parts_input_body, op['parameter_order']) op['header'] = headers try: element = list(op['input'].values())[0] ns_uri = element.namespaces[None] qualified = element.qualified except (AttributeError, KeyError) as e: # TODO: fix if no parameters parsed or "variants" ns_uri = op['namespace'] qualified = None if ns_uri: op['namespace'] = ns_uri op['qualified'] = qualified # Remove temporary property del op['input_msg'] else: op['input'] = None op['header'] = None output = operation_node('output', error=False) body = output and output('body', ns=list(soap_uris.values()), error=False) parts_output_body = body and body['parts'] or None if 'output_msg' in op: op['output'] = get_message(messages, op['output_msg'], parts_output_body) # Remove temporary property del op['output_msg'] else: op['output'] = None if 'fault_msgs' in op: faults = op['faults'] = {} for name, msg in op['fault_msgs'].iteritems(): msg_obj = get_message(messages, msg, parts_output_body) tag_name = msg_obj.keys()[0] faults[tag_name] = msg_obj # useless? never used parts_output_headers = [] headers = output and output('header', ns=list(soap_uris.values()), error=False) for header in headers or []: hdr = {'message': header['message'], 'part': header['part']} parts_output_headers.append(hdr) for service in wsdl("service", error=False) or []: service_name = service['name'] if not service_name: continue # empty service? serv = services.setdefault(service_name, {}) ports = serv['ports'] = {} serv['documentation'] = service['documentation'] or '' for port in service.port: binding_name = get_local_name(port['binding']) if not binding_name in bindings: continue # unknown binding binding = ports[port['name']] = copy.deepcopy(bindings[binding_name]) address = port('address', ns=list(soap_uris.values()), error=False) location = address and address['location'] or None soap_uri = address and soap_uris.get(address.get_prefix()) soap_ver = soap_uri and self.soap_ns_uris.get(soap_uri) binding.update({ 'location': location, 'service_name': service_name, 'soap_uri': soap_uri, 'soap_ver': soap_ver, }) # create an default service if none is given in the wsdl: if not services: services[''] = {'ports': {'': None}} return services def wsdl_parse(self, url, cache=False): """Parse Web Service Description v1.1""" log.debug('Parsing wsdl url: %s' % url) # Try to load a previously parsed wsdl: force_download = False if cache: # make md5 hash of the url for caching... filename_pkl = '%s.pkl' % hashlib.md5(url).hexdigest() if isinstance(cache, basestring): filename_pkl = os.path.join(cache, filename_pkl) if os.path.exists(filename_pkl): log.debug('Unpickle file %s' % (filename_pkl, )) f = open(filename_pkl, 'r') pkl = pickle.load(f) f.close() # sanity check: if pkl['version'][:-1] != __version__.split(' ')[0][:-1] or pkl['url'] != url: warnings.warn('version or url mismatch! discarding cached wsdl', RuntimeWarning) log.debug('Version: %s %s' % (pkl['version'], __version__)) log.debug('URL: %s %s' % (pkl['url'], url)) force_download = True else: self.namespace = pkl['namespace'] self.documentation = pkl['documentation'] return pkl['services'] # always return an unicode object: REVERSE_TYPE_MAP['string'] = str wsdl = self._url_to_xml_tree(url, cache, force_download) services = self._xml_tree_to_services(wsdl, cache, force_download) # dump the full service/port/operation map #log.debug(pprint.pformat(services)) # Save parsed wsdl (cache) if cache: f = open(filename_pkl, "wb") pkl = { 'version': __version__.split(' ')[0], 'url': url, 'namespace': self.namespace, 'documentation': self.documentation, 'services': services, } pickle.dump(pkl, f) f.close() return services def __setitem__(self, item, value): """Set SOAP Header value - this header will be sent for every request.""" self.__headers[item] = value def close(self): """Finish the connection and remove temp files""" self.http.close() if self.cacert.startswith(tempfile.gettempdir()): log.debug('removing %s' % self.cacert) os.unlink(self.cacert) def parse_proxy(proxy_str): """Parses proxy address user:pass@host:port into a dict suitable for httplib2""" proxy_dict = {} if proxy_str is None: return if '@' in proxy_str: user_pass, host_port = proxy_str.split('@') else: user_pass, host_port = '', proxy_str if ':' in host_port: host, port = host_port.split(':') proxy_dict['proxy_host'], proxy_dict['proxy_port'] = host, int(port) if ':' in user_pass: proxy_dict['proxy_user'], proxy_dict['proxy_pass'] = user_pass.split(':') return proxy_dict if __name__ == '__main__': pass pysimplesoap-1.16/pysimplesoap/helpers.py000066400000000000000000000605701245630617100207440ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Pythonic simple SOAP Client helpers""" from __future__ import unicode_literals import sys if sys.version > '3': basestring = unicode = str import datetime from decimal import Decimal import os import logging import hashlib import warnings try: import urllib2 from urlparse import urlsplit except ImportError: from urllib import request as urllib2 from urllib.parse import urlsplit from . import __author__, __copyright__, __license__, __version__ log = logging.getLogger(__name__) def fetch(url, http, cache=False, force_download=False, wsdl_basedir='', headers={}): """Download a document from a URL, save it locally if cache enabled""" # check / append a valid schema if not given: url_scheme, netloc, path, query, fragment = urlsplit(url) if not url_scheme in ('http', 'https', 'file'): for scheme in ('http', 'https', 'file'): try: path = os.path.normpath(os.path.join(wsdl_basedir, url)) if not url.startswith("/") and scheme in ('http', 'https'): tmp_url = "%s://%s" % (scheme, path) else: tmp_url = "%s:%s" % (scheme, path) log.debug('Scheme not found, trying %s' % scheme) return fetch(tmp_url, http, cache, force_download, wsdl_basedir, headers) except Exception as e: log.error(e) raise RuntimeError('No scheme given for url: %s' % url) # make md5 hash of the url for caching... filename = '%s.xml' % hashlib.md5(url.encode('utf8')).hexdigest() if isinstance(cache, basestring): filename = os.path.join(cache, filename) if cache and os.path.exists(filename) and not force_download: log.info('Reading file %s' % filename) f = open(filename, 'r') xml = f.read() f.close() else: if url_scheme == 'file': log.info('Fetching url %s using urllib2' % url) f = urllib2.urlopen(url) xml = f.read() else: log.info('GET %s using %s' % (url, http._wrapper_version)) response, xml = http.request(url, 'GET', None, headers) if cache: log.info('Writing file %s' % filename) if not os.path.isdir(cache): os.makedirs(cache) f = open(filename, 'w') f.write(xml) f.close() return xml def sort_dict(od, d): """Sort parameters (same order as xsd:sequence)""" if isinstance(od, dict): ret = Struct() for k in od.keys(): v = d.get(k) # don't append null tags! if v is not None: if isinstance(v, dict): v = sort_dict(od[k], v) elif isinstance(v, list): v = [sort_dict(od[k][0], v1) for v1 in v] ret[k] = v if hasattr(od, 'namespaces'): ret.namespaces.update(od.namespaces) ret.references.update(od.references) ret.qualified = od.qualified return ret else: return d def make_key(element_name, element_type, namespace): """Return a suitable key for elements""" # only distinguish 'element' vs other types if element_type in ('complexType', 'simpleType'): eltype = 'complexType' else: eltype = element_type if eltype not in ('element', 'complexType', 'simpleType'): raise RuntimeError("Unknown element type %s = %s" % (element_name, eltype)) return (element_name, eltype, namespace) def process_element(elements, element_name, node, element_type, xsd_uri, dialect, namespace, qualified=None, soapenc_uri='http://schemas.xmlsoap.org/soap/encoding/', struct=None): """Parse and define simple element types as Struct objects""" log.debug('Processing element %s %s' % (element_name, element_type)) # iterate over inner tags of the element definition: for tag in node: # sanity checks (skip superfluous xml tags, resolve aliases, etc.): if tag.get_local_name() in ('annotation', 'documentation'): continue elif tag.get_local_name() in ('element', 'restriction', 'list'): log.debug('%s has no children! %s' % (element_name, tag)) children = tag # element "alias"? alias = True elif tag.children(): children = tag.children() alias = False else: log.debug('%s has no children! %s' % (element_name, tag)) continue # TODO: abstract? # check if extending a previous processed element ("extension"): new_struct = struct is None if new_struct: struct = Struct() struct.namespaces[None] = namespace # set the default namespace struct.qualified = qualified # iterate over the element's components (sub-elements): for e in children: # extract type information from xml attributes / children: t = e['type'] if not t: t = e['itemType'] # xs:list if not t: t = e['base'] # complexContent (extension)! if not t: t = e['ref'] # reference to another element if not t: # "anonymous" elements had no type attribute but children if e['name'] and e.children(): # create a type name to process the children t = "%s_%s" % (element_name, e['name']) c = e.children() et = c.get_local_name() c = c.children() process_element(elements, t, c, et, xsd_uri, dialect, namespace, qualified) else: t = 'anyType' # no type given! # extract namespace uri and type from xml attribute: t = t.split(":") if len(t) > 1: ns, type_name = t else: ns, type_name = None, t[0] uri = ns and e.get_namespace_uri(ns) or xsd_uri # look for the conversion function (python type) if uri in (xsd_uri, soapenc_uri) and type_name != 'Array': # look for the type, None == any fn = REVERSE_TYPE_MAP.get(type_name, None) if tag.get_local_name() == 'list': # simple list type (values separated by spaces) fn = lambda s: [fn(v) for v in s.split(" ")] elif (uri == soapenc_uri and type_name == 'Array'): # arrays of simple types (look at the attribute tags): fn = [] for a in e.children(): for k, v in a[:]: if k.endswith(":arrayType"): type_name = v fn_namespace = None if ":" in type_name: fn_uri, type_name = type_name.split(":") fn_namespace = e.get_namespace_uri(fn_uri) if "[]" in type_name: type_name = type_name[:type_name.index("[]")] # get the scalar conversion function (if any) fn_array = REVERSE_TYPE_MAP.get(type_name, None) if fn_array is None and type_name != "anyType" and fn_namespace: # get the complext element: ref_type = "complexType" key = make_key(type_name, ref_type, fn_namespace) fn_complex = elements.setdefault(key, Struct()) # create an indirect struct {type_name: ...}: fn_array = Struct() fn_array[type_name] = fn_complex fn_array.namespaces[None] = fn_namespace # set the default namespace fn_array.qualified = qualified fn.append(fn_array) else: # not a simple python type / conversion function not available fn = None if not fn: # simple / complex type, postprocess later if ns: fn_namespace = uri # use the specified namespace else: fn_namespace = namespace # use parent namespace (default) for k, v in e[:]: if k.startswith("xmlns:"): # get the namespace uri from the element fn_namespace = v # create and store an empty python element (dict) filled later if not e['ref']: ref_type = "complexType" else: ref_type = "element" key = make_key(type_name, ref_type, fn_namespace) fn = elements.setdefault(key, Struct()) if e['maxOccurs'] == 'unbounded' or (uri == soapenc_uri and type_name == 'Array'): # it's an array... TODO: compound arrays? and check ns uri! if isinstance(fn, Struct): if len(children) > 1 or (dialect in ('jetty', )): # Jetty style support # {'ClassName': [{'attr1': val1, 'attr2': val2}] fn.array = True else: # .NET style support (backward compatibility) # [{'ClassName': {'attr1': val1, 'attr2': val2}] struct.array = True else: if len(children) > 1 or dialect in ('jetty',): # Jetty style support # scalar array support {'attr1': [val1]} fn = [fn] else: # Jetty.NET style support (backward compatibility) # scalar array support [{'attr1': val1}] struct.array = True # store the sub-element python type (function) in the element dict if (e['name'] is not None and not alias) or e['ref']: e_name = e['name'] or type_name # for refs, use the type name struct[e_name] = fn struct.references[e_name] = e['ref'] struct.namespaces[e_name] = namespace # set the element namespace else: log.debug('complexContent/simpleType/element %s = %s' % (element_name, type_name)) # use None to point this is a complex element reference struct.refers_to = fn if e is not None and e.get_local_name() == 'extension' and e.children(): # extend base element (if ComplexContent only!): if isinstance(fn, Struct) and fn.refers_to: base_struct = fn.refers_to else: # TODO: check if this actually works for SimpleContent base_struct = None # extend base element: process_element(elements, element_name, e.children(), element_type, xsd_uri, dialect, namespace, qualified, struct=base_struct) # add the processed element to the main dictionary (if not extension): if new_struct: key = make_key(element_name, element_type, namespace) elements.setdefault(key, Struct()).update(struct) def postprocess_element(elements, processed): """Fix unresolved references""" # (elements referenced before its definition, thanks .net) # avoid already processed elements: if elements in processed: return processed.append(elements) for k, v in elements.items(): if isinstance(v, Struct): if v != elements: # TODO: fix recursive elements try: postprocess_element(v, processed) except RuntimeError as e: # maximum recursion depth exceeded warnings.warn(unicode(e), RuntimeWarning) if v.refers_to: # extension base? if isinstance(v.refers_to, dict): for i, kk in enumerate(v.refers_to): # extend base -keep orginal order- if isinstance(v.refers_to, Struct): elements[k].insert(kk, v.refers_to[kk], i) # update namespace (avoid ArrayOfKeyValueOfanyTypeanyType) if isinstance(v.refers_to, Struct) and v.refers_to.namespaces and kk: elements[k].namespaces[kk] = v.refers_to.namespaces[kk] elements[k].references[kk] = v.refers_to.references[kk] # clean the reference: v.refers_to = None else: # "alias", just replace ##log.debug('Replacing %s = %s' % (k, v.refers_to)) elements[k] = v.refers_to if v.array: elements[k] = [v] # convert arrays to python lists if isinstance(v, list): for n in v: # recurse list if isinstance(n, (Struct, list)): #if n != elements: # TODO: fix recursive elements postprocess_element(n, processed) def get_message(messages, message_name, part_name, parameter_order=None): if part_name: # get the specific part of the message: return messages.get((message_name, part_name)) else: # get the first part for the specified message: parts = {} for (message_name_key, part_name_key), message in messages.items(): if message_name_key == message_name: parts[part_name_key] = message if len(parts)>1: # merge (sorted by parameter_order for rpc style) new_msg = None for part_name_key in parameter_order: part = parts.get(part_name_key) if not part: log.error('Part %s not found for %s' % (part_name_key, message_name)) elif not new_msg: new_msg = part.copy() else: new_msg[message_name].update(part[message_name]) return new_msg elif parts: return list(parts.values())[0] #return parts.values()[0] get_local_name = lambda s: s and str((':' in s) and s.split(':')[1] or s) get_namespace_prefix = lambda s: s and str((':' in s) and s.split(':')[0] or None) def preprocess_schema(schema, imported_schemas, elements, xsd_uri, dialect, http, cache, force_download, wsdl_basedir, global_namespaces=None, qualified=False): """Find schema elements and complex types""" from .simplexml import SimpleXMLElement # here to avoid recursive imports # analyze the namespaces used in this schema local_namespaces = {} for k, v in schema[:]: if k.startswith("xmlns"): local_namespaces[get_local_name(k)] = v if k == 'targetNamespace': # URI namespace reference for this schema if v == "urn:DefaultNamespace": v = global_namespaces[None] local_namespaces[None] = v if k == 'elementFormDefault': qualified = (v == "qualified") # add schema namespaces to the global namespace dict = {URI: ns prefix} for ns in local_namespaces.values(): if ns not in global_namespaces: global_namespaces[ns] = 'ns%s' % len(global_namespaces) for element in schema.children() or []: if element.get_local_name() in ('import', 'include',): schema_namespace = element['namespace'] schema_location = element['schemaLocation'] if schema_location is None: log.debug('Schema location not provided for %s!' % schema_namespace) continue if schema_location in imported_schemas: log.debug('Schema %s already imported!' % schema_location) continue imported_schemas[schema_location] = schema_namespace log.debug('Importing schema %s from %s' % (schema_namespace, schema_location)) # Open uri and read xml: xml = fetch(schema_location, http, cache, force_download, wsdl_basedir) # recalculate base path for relative schema locations path = os.path.normpath(os.path.join(wsdl_basedir, schema_location)) path = os.path.dirname(path) # Parse imported XML schema (recursively): imported_schema = SimpleXMLElement(xml, namespace=xsd_uri) preprocess_schema(imported_schema, imported_schemas, elements, xsd_uri, dialect, http, cache, force_download, path, global_namespaces, qualified) element_type = element.get_local_name() if element_type in ('element', 'complexType', "simpleType"): namespace = local_namespaces[None] # get targetNamespace element_ns = global_namespaces[ns] # get the prefix element_name = element['name'] log.debug("Parsing Element %s: %s" % (element_type, element_name)) if element.get_local_name() == 'complexType': children = element.children() elif element.get_local_name() == 'simpleType': children = element('restriction', ns=xsd_uri, error=False) if not children: children = element.children() # xs:list elif element.get_local_name() == 'element' and element['type']: children = element else: children = element.children() if children: children = children.children() elif element.get_local_name() == 'element': children = element if children: process_element(elements, element_name, children, element_type, xsd_uri, dialect, namespace, qualified) # simplexml utilities: try: _strptime = datetime.datetime.strptime except AttributeError: # python2.4 _strptime = lambda s, fmt: datetime.datetime(*(time.strptime(s, fmt)[:6])) # Functions to serialize/deserialize special immutable types: def datetime_u(s): fmt = "%Y-%m-%dT%H:%M:%S" try: return _strptime(s, fmt) except ValueError: try: # strip utc offset if s[-3] == ":" and s[-6] in (' ', '-', '+'): try: import iso8601 return iso8601.parse_date(s) except ImportError: pass try: import isodate return isodate.parse_datetime(s) except ImportError: pass try: import dateutil.parser return dateutil.parser.parse(s) except ImportError: pass warnings.warn('removing unsupported UTC offset. Install `iso8601`, `isodate` or `python-dateutil` package to support it', RuntimeWarning) s = s[:-6] # parse microseconds try: return _strptime(s, fmt + ".%f") except: return _strptime(s, fmt) except ValueError: # strip microseconds (not supported in this platform) if "." in s: warnings.warn('removing unsuppported microseconds', RuntimeWarning) s = s[:s.index(".")] return _strptime(s, fmt) datetime_m = lambda dt: dt.isoformat() date_u = lambda s: _strptime(s[0:10], "%Y-%m-%d").date() date_m = lambda d: d.strftime("%Y-%m-%d") time_u = lambda s: _strptime(s, "%H:%M:%S").time() time_m = lambda d: d.strftime("%H%M%S") bool_u = lambda s: {'0': False, 'false': False, '1': True, 'true': True}[s] bool_m = lambda s: {False: 'false', True: 'true'}[s] decimal_m = lambda d: '{0:f}'.format(d) float_m = lambda f: '{0:.10f}'.format(f) # aliases: class Alias(object): def __init__(self, py_type, xml_type): self.py_type, self.xml_type = py_type, xml_type def __call__(self, value): return self.py_type(value) def __repr__(self): return "" % (self.xml_type, self.py_type) if sys.version > '3': long = Alias(int, 'long') byte = Alias(str, 'byte') short = Alias(int, 'short') double = Alias(float, 'double') integer = Alias(long, 'integer') DateTime = datetime.datetime Date = datetime.date Time = datetime.time duration = Alias(str, 'duration') any_uri = Alias(str, 'anyURI') # Define convertion function (python type): xml schema type TYPE_MAP = { unicode: 'string', bool: 'boolean', short: 'short', byte: 'byte', int: 'int', long: 'long', integer: 'integer', float: 'float', double: 'double', Decimal: 'decimal', datetime.datetime: 'dateTime', datetime.date: 'date', datetime.time: 'time', duration: 'duration', any_uri: 'anyURI', } TYPE_MARSHAL_FN = { datetime.datetime: datetime_m, datetime.date: date_m, datetime.time: time_m, float: float_m, Decimal: decimal_m, bool: bool_m, } TYPE_UNMARSHAL_FN = { datetime.datetime: datetime_u, datetime.date: date_u, datetime.time: time_u, bool: bool_u, str: unicode, } REVERSE_TYPE_MAP = dict([(v, k) for k, v in TYPE_MAP.items()]) REVERSE_TYPE_MAP.update({ 'base64Binary': str, }) # insert str here to avoid collision in REVERSE_TYPE_MAP (i.e. decoding errors) if str not in TYPE_MAP: TYPE_MAP[str] = 'string' class Struct(dict): """Minimal ordered dictionary to represent elements (i.e. xsd:sequences)""" def __init__(self): self.__keys = [] self.array = False self.namespaces = {} # key: element, value: namespace URI self.references = {} # key: element, value: reference name self.refers_to = None # "symbolic linked" struct self.qualified = None def __setitem__(self, key, value): if key not in self.__keys: self.__keys.append(key) dict.__setitem__(self, key, value) def insert(self, key, value, index=0): if key not in self.__keys: self.__keys.insert(index, key) dict.__setitem__(self, key, value) def __delitem__(self, key): if key in self.__keys: self.__keys.remove(key) dict.__delitem__(self, key) def __iter__(self): return iter(self.__keys) def keys(self): return self.__keys def items(self): return [(key, self[key]) for key in self.__keys] def update(self, other): for k, v in other.items(): self[k] = v # do not change if we are an array but the other is not: if isinstance(other, Struct) and not self.array: self.array = other.array if isinstance(other, Struct): # TODO: check replacing default ns is a regression self.namespaces.update(other.namespaces) self.references.update(other.references) self.qualified = other.qualified self.refers_to = other.refers_to def copy(self): "Make a duplicate" new = Struct() new.update(self) return new def __str__(self): return "%s" % dict.__str__(self) def __repr__(self): try: s = "{%s}" % ", ".join(['%s: %s' % (repr(k), repr(v)) for k, v in self.items()]) except RuntimeError as e: # maximum recursion depth exceeded s = "{%s}" % ", ".join(['%s: %s' % (repr(k), unicode(e)) for k, v in self.items()]) warnings.warn(unicode(e), RuntimeWarning) if self.array and False: s = "[%s]" % s return s pysimplesoap-1.16/pysimplesoap/server.py000066400000000000000000000606041245630617100206060ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Pythonic simple SOAP Server implementation""" from __future__ import unicode_literals import sys if sys.version > '3': unicode = str import datetime import sys import logging import warnings import re import traceback try: from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer except ImportError: from http.server import BaseHTTPRequestHandler, HTTPServer from . import __author__, __copyright__, __license__, __version__ from .simplexml import SimpleXMLElement, TYPE_MAP, Date, Decimal log = logging.getLogger(__name__) # Deprecated? NS_RX = re.compile(r'xmlns:(\w+)="(.+?)"') class SoapFault(Exception): def __init__(self, faultcode=None, faultstring=None, detail=None): self.faultcode = faultcode or self.__class__.__name__ self.faultstring = faultstring or '' self.detail = detail class SoapDispatcher(object): """Simple Dispatcher for SOAP Server""" def __init__(self, name, documentation='', action='', location='', namespace=None, prefix=False, soap_uri="http://schemas.xmlsoap.org/soap/envelope/", soap_ns='soap', namespaces={}, pretty=False, debug=False, **kwargs): """ :param namespace: Target namespace; xmlns=targetNamespace :param prefix: Prefix for target namespace; xmlns:prefix=targetNamespace :param namespaces: Specify additional namespaces; example: {'external': 'http://external.mt.moboperator'} :param pretty: Prettifies generated xmls :param debug: Use to add tracebacks in generated xmls. Multiple namespaces =================== It is possible to support multiple namespaces. You need to specify additional namespaces by passing `namespace` parameter. >>> dispatcher = SoapDispatcher( ... name = "MTClientWS", ... location = "http://localhost:8008/ws/MTClientWS", ... action = 'http://localhost:8008/ws/MTClientWS', # SOAPAction ... namespace = "http://external.mt.moboperator", prefix="external", ... documentation = 'moboperator MTClientWS', ... namespaces = { ... 'external': 'http://external.mt.moboperator', ... 'model': 'http://model.common.mt.moboperator' ... }, ... ns = True) Now the registered method must return node names with namespaces' prefixes. >>> def _multi_ns_func(self, serviceMsisdn): ... ret = { ... 'external:activateSubscriptionsReturn': [ ... {'model:code': '0'}, ... {'model:description': 'desc'}, ... ]} ... return ret Our prefixes will be changed to those used by the client. """ self.methods = {} self.name = name self.documentation = documentation self.action = action # base SoapAction self.location = location self.namespace = namespace # targetNamespace self.prefix = prefix self.soap_ns = soap_ns self.soap_uri = soap_uri self.namespaces = namespaces self.pretty = pretty self.debug = debug @staticmethod def _extra_namespaces(xml, ns): """Extends xml with extra namespaces. :param ns: dict with namespaceUrl:prefix pairs :param xml: XML node to modify """ if ns: _tpl = 'xmlns:%s="%s"' _ns_str = " ".join([_tpl % (prefix, uri) for uri, prefix in ns.items() if uri not in xml]) xml = xml.replace('/>', ' ' + _ns_str + '/>') return xml def register_function(self, name, fn, returns=None, args=None, doc=None): self.methods[name] = fn, returns, args, doc or getattr(fn, "__doc__", "") def response_element_name(self, method): return '%sResponse' % method def dispatch(self, xml, action=None, fault=None): """Receive and process SOAP call, returns the xml""" # a dict can be sent in fault to expose it to the caller # default values: prefix = self.prefix ret = None if fault is None: fault = {} soap_ns, soap_uri = self.soap_ns, self.soap_uri soap_fault_code = 'VersionMismatch' name = None # namespaces = [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')] _ns_reversed = dict(((v, k) for k, v in self.namespaces.items())) # Switch keys-values # _ns_reversed = {'http://external.mt.moboperator': 'external', 'http://model.common.mt.moboperator': 'model'} try: request = SimpleXMLElement(xml, namespace=self.namespace) # detect soap prefix and uri (xmlns attributes of Envelope) for k, v in request[:]: if v in ("http://schemas.xmlsoap.org/soap/envelope/", "http://www.w3.org/2003/05/soap-env", "http://www.w3.org/2003/05/soap-envelope",): soap_ns = request.attributes()[k].localName soap_uri = request.attributes()[k].value # If the value from attributes on Envelope is in additional namespaces elif v in self.namespaces.values(): _ns = request.attributes()[k].localName _uri = request.attributes()[k].value _ns_reversed[_uri] = _ns # update with received alias # Now we change 'external' and 'model' to the received forms i.e. 'ext' and 'mod' # After that we know how the client has prefixed additional namespaces ns = NS_RX.findall(xml) for k, v in ns: if v in self.namespaces.values(): _ns_reversed[v] = k soap_fault_code = 'Client' # parse request message and get local method method = request('Body', ns=soap_uri).children()(0) if action: # method name = action name = action[len(self.action)+1:-1] prefix = self.prefix if not action or not name: # method name = input message name name = method.get_local_name() prefix = method.get_prefix() log.debug('dispatch method: %s', name) function, returns_types, args_types, doc = self.methods[name] log.debug('returns_types %s', returns_types) # de-serialize parameters (if type definitions given) if args_types: args = method.children().unmarshall(args_types) elif args_types is None: args = {'request': method} # send raw request else: args = {} # no parameters soap_fault_code = 'Server' # execute function ret = function(**args) log.debug('dispathed method returns: %s', ret) except SoapFault as e: fault.update({ 'faultcode': "%s.%s" % (soap_fault_code, e.faultcode), 'faultstring': e.faultstring, 'detail': e.detail }) except Exception: # This shouldn't be one huge try/except import sys etype, evalue, etb = sys.exc_info() log.error(traceback.format_exc()) if self.debug: detail = ''.join(traceback.format_exception(etype, evalue, etb)) detail += '\n\nXML REQUEST\n\n' + xml else: detail = None fault.update({'faultcode': "%s.%s" % (soap_fault_code, etype.__name__), 'faultstring': evalue, 'detail': detail}) # build response message if not prefix: xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>""" else: xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s" xmlns:%(prefix)s="%(namespace)s"/>""" xml %= { # a %= {} is a shortcut for a = a % {} 'namespace': self.namespace, 'prefix': prefix, 'soap_ns': soap_ns, 'soap_uri': soap_uri } # Now we add extra namespaces xml = SoapDispatcher._extra_namespaces(xml, _ns_reversed) # Change our namespace alias to that given by the client. # We put [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')] # mix it with {'http://external.mt.moboperator': 'ext', 'http://model.common.mt.moboperator': 'mod'} mapping = dict(((k, _ns_reversed[v]) for k, v in self.namespaces.items())) # Switch keys-values and change value # and get {'model': u'mod', 'external': u'ext'} response = SimpleXMLElement(xml, namespace=self.namespace, namespaces_map=mapping, prefix=prefix) response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance" response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema" body = response.add_child("%s:Body" % soap_ns, ns=False) if fault: # generate a Soap Fault (with the python exception) body.marshall("%s:Fault" % soap_ns, fault, ns=False) else: # return normal value res = body.add_child(self.response_element_name(name), ns=self.namespace) if not prefix: res['xmlns'] = self.namespace # add target namespace # serialize returned values (response) if type definition available if returns_types: # TODO: full sanity check of type structure (recursive) complex_type = isinstance(ret, dict) if complex_type: # check if type mapping correlates with return value types_ok = all([k in returns_types for k in ret.keys()]) if not types_ok: warnings.warn("Return value doesn't match type structure: " "%s vs %s" % (str(returns_types), str(ret))) if not complex_type or not types_ok: # backward compatibility for scalar and simple types res.marshall(list(returns_types.keys())[0], ret, ) else: # new style for complex classes for k, v in ret.items(): res.marshall(k, v) elif returns_types is None: # merge xmlelement returned res.import_node(ret) elif returns_types == {}: log.warning('Given returns_types is an empty dict.') return response.as_xml(pretty=self.pretty) # Introspection functions: def list_methods(self): """Return a list of aregistered operations""" return [(method, doc) for method, (function, returns, args, doc) in self.methods.items()] def help(self, method=None): """Generate sample request and response messages""" (function, returns, args, doc) = self.methods[method] xml = """ <%(method)s xmlns="%(namespace)s"/> """ % {'method': method, 'namespace': self.namespace} request = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix) if args: items = args.items() elif args is None: items = [('value', None)] else: items = [] for k, v in items: request(method).marshall(k, v, add_comments=True, ns=False) xml = """ <%(method)sResponse xmlns="%(namespace)s"/> """ % {'method': method, 'namespace': self.namespace} response = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix) if returns: items = returns.items() elif args is None: items = [('value', None)] else: items = [] for k, v in items: response('%sResponse' % method).marshall(k, v, add_comments=True, ns=False) return request.as_xml(pretty=True), response.as_xml(pretty=True), doc def wsdl(self): """Generate Web Service Description v1.1""" xml = """ %(documentation)s """ % {'namespace': self.namespace, 'name': self.name, 'documentation': self.documentation} wsdl = SimpleXMLElement(xml) for method, (function, returns, args, doc) in self.methods.items(): # create elements: def parse_element(name, values, array=False, complex=False): if not complex: element = wsdl('wsdl:types')('xsd:schema').add_child('xsd:element') complex = element.add_child("xsd:complexType") else: complex = wsdl('wsdl:types')('xsd:schema').add_child('xsd:complexType') element = complex element['name'] = name if values: items = values elif values is None: items = [('value', None)] else: items = [] if not array and items: all = complex.add_child("xsd:all") elif items: all = complex.add_child("xsd:sequence") for k, v in items: e = all.add_child("xsd:element") e['name'] = k if array: e[:] = {'minOccurs': "0", 'maxOccurs': "unbounded"} if v in TYPE_MAP.keys(): t = 'xsd:%s' % TYPE_MAP[v] elif v is None: t = 'xsd:anyType' elif isinstance(v, list): n = "ArrayOf%s%s" % (name, k) l = [] for d in v: l.extend(d.items()) parse_element(n, l, array=True, complex=True) t = "tns:%s" % n elif isinstance(v, dict): n = "%s%s" % (name, k) parse_element(n, v.items(), complex=True) t = "tns:%s" % n else: raise TypeError("unknonw type v for marshalling" % str(v)) e.add_attribute('type', t) parse_element("%s" % method, args and args.items()) parse_element("%sResponse" % method, returns and returns.items()) # create messages: for m, e in ('Input', ''), ('Output', 'Response'): message = wsdl.add_child('wsdl:message') message['name'] = "%s%s" % (method, m) part = message.add_child("wsdl:part") part[:] = {'name': 'parameters', 'element': 'tns:%s%s' % (method, e)} # create ports portType = wsdl.add_child('wsdl:portType') portType['name'] = "%sPortType" % self.name for method, (function, returns, args, doc) in self.methods.items(): op = portType.add_child('wsdl:operation') op['name'] = method if doc: op.add_child("wsdl:documentation", doc) input = op.add_child("wsdl:input") input['message'] = "tns:%sInput" % method output = op.add_child("wsdl:output") output['message'] = "tns:%sOutput" % method # create bindings binding = wsdl.add_child('wsdl:binding') binding['name'] = "%sBinding" % self.name binding['type'] = "tns:%sPortType" % self.name soapbinding = binding.add_child('soap:binding') soapbinding['style'] = "document" soapbinding['transport'] = "http://schemas.xmlsoap.org/soap/http" for method in self.methods.keys(): op = binding.add_child('wsdl:operation') op['name'] = method soapop = op.add_child('soap:operation') soapop['soapAction'] = self.action + method soapop['style'] = 'document' input = op.add_child("wsdl:input") ##input.add_attribute('name', "%sInput" % method) soapbody = input.add_child("soap:body") soapbody["use"] = "literal" output = op.add_child("wsdl:output") ##output.add_attribute('name', "%sOutput" % method) soapbody = output.add_child("soap:body") soapbody["use"] = "literal" service = wsdl.add_child('wsdl:service') service["name"] = "%sService" % self.name service.add_child('wsdl:documentation', text=self.documentation) port = service.add_child('wsdl:port') port["name"] = "%s" % self.name port["binding"] = "tns:%sBinding" % self.name soapaddress = port.add_child('soap:address') soapaddress["location"] = self.location return wsdl.as_xml(pretty=True) class SOAPHandler(BaseHTTPRequestHandler): def do_GET(self): """User viewable help information and wsdl""" args = self.path[1:].split("?") if self.path != "/" and args[0] not in self.server.dispatcher.methods.keys(): self.send_error(404, "Method not found: %s" % args[0]) else: if self.path == "/": # return wsdl if no method supplied response = self.server.dispatcher.wsdl() else: # return supplied method help (?request or ?response messages) req, res, doc = self.server.dispatcher.help(args[0]) if len(args) == 1 or args[1] == "request": response = req else: response = res self.send_response(200) self.send_header("Content-type", "text/xml") self.end_headers() self.wfile.write(response) def do_POST(self): """SOAP POST gateway""" request = self.rfile.read(int(self.headers.get('content-length'))) # convert xml request to unicode (according to request headers) if sys.version < '3': encoding = self.headers.getparam("charset") else: encoding = self.headers.get_param("charset") request = request.decode(encoding) fault = {} # execute the method response = self.server.dispatcher.dispatch(request, fault=fault) # check if fault dict was completed (faultcode, faultstring, detail) if fault: self.send_response(500) else: self.send_response(200) self.send_header("Content-type", "text/xml") self.end_headers() self.wfile.write(response) class WSGISOAPHandler(object): def __init__(self, dispatcher): self.dispatcher = dispatcher def __call__(self, environ, start_response): return self.handler(environ, start_response) def handler(self, environ, start_response): if environ['REQUEST_METHOD'] == 'GET': return self.do_get(environ, start_response) elif environ['REQUEST_METHOD'] == 'POST': return self.do_post(environ, start_response) else: start_response('405 Method not allowed', [('Content-Type', 'text/plain')]) return ['Method not allowed'] def do_get(self, environ, start_response): path = environ.get('PATH_INFO').lstrip('/') query = environ.get('QUERY_STRING') if path != "" and path not in self.dispatcher.methods.keys(): start_response('404 Not Found', [('Content-Type', 'text/plain')]) return ["Method not found: %s" % path] elif path == "": # return wsdl if no method supplied response = self.dispatcher.wsdl() else: # return supplied method help (?request or ?response messages) req, res, doc = self.dispatcher.help(path) if len(query) == 0 or query == "request": response = req else: response = res start_response('200 OK', [('Content-Type', 'text/xml'), ('Content-Length', str(len(response)))]) return [response] def do_post(self, environ, start_response): length = int(environ['CONTENT_LENGTH']) request = environ['wsgi.input'].read(length) response = self.dispatcher.dispatch(request) start_response('200 OK', [('Content-Type', 'text/xml'), ('Content-Length', str(len(response)))]) return [response] if __name__ == "__main__": dispatcher = SoapDispatcher( name="PySimpleSoapSample", location="http://localhost:8008/", action='http://localhost:8008/', # SOAPAction namespace="http://example.com/pysimplesoapsamle/", prefix="ns0", documentation='Example soap service using PySimpleSoap', trace=True, debug=True, ns=True) def adder(p, c, dt=None): """Add several values""" dt = dt + datetime.timedelta(365) return {'ab': p['a'] + p['b'], 'dd': c[0]['d'] + c[1]['d'], 'dt': dt} def dummy(in0): """Just return input""" return in0 def echo(request): """Copy request->response (generic, any type)""" return request.value dispatcher.register_function( 'Adder', adder, returns={'AddResult': {'ab': int, 'dd': unicode, 'dt': datetime.date}}, args={'p': {'a': int, 'b': int}, 'dt': Date, 'c': [{'d': Decimal}]} ) dispatcher.register_function( 'Dummy', dummy, returns={'out0': str}, args={'in0': str} ) dispatcher.register_function('Echo', echo) if '--local' in sys.argv: wsdl = dispatcher.wsdl() for method, doc in dispatcher.list_methods(): request, response, doc = dispatcher.help(method) if '--serve' in sys.argv: log.info("Starting server...") httpd = HTTPServer(("", 8008), SOAPHandler) httpd.dispatcher = dispatcher httpd.serve_forever() if '--wsgi-serve' in sys.argv: log.info("Starting wsgi server...") from wsgiref.simple_server import make_server application = WSGISOAPHandler(dispatcher) wsgid = make_server('', 8008, application) wsgid.serve_forever() if '--consume' in sys.argv: from .client import SoapClient client = SoapClient( location="http://localhost:8008/", action='http://localhost:8008/', # SOAPAction namespace="http://example.com/sample.wsdl", soap_ns='soap', trace=True, ns="ns0", ) p = {'a': 1, 'b': 2} c = [{'d': '1.20'}, {'d': '2.01'}] response = client.Adder(p=p, dt='2010-07-24', c=c) result = response.AddResult log.info(int(result.ab)) log.info(str(result.dd)) if '--consume-wsdl' in sys.argv: from .client import SoapClient client = SoapClient( wsdl="http://localhost:8008/", ) p = {'a': 1, 'b': 2} c = [{'d': '1.20'}, {'d': '2.01'}] dt = datetime.date.today() response = client.Adder(p=p, dt=dt, c=c) result = response['AddResult'] log.info(int(result['ab'])) log.info(str(result['dd'])) pysimplesoap-1.16/pysimplesoap/simplexml.py000066400000000000000000000521741245630617100213150ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Simple XML manipulation""" from __future__ import unicode_literals import sys if sys.version > '3': basestring = str unicode = str import logging import re import time import xml.dom.minidom from . import __author__, __copyright__, __license__, __version__ # Utility functions used for marshalling, moved aside for readability from .helpers import TYPE_MAP, TYPE_MARSHAL_FN, TYPE_UNMARSHAL_FN, \ REVERSE_TYPE_MAP, Struct, Date, Decimal log = logging.getLogger(__name__) class SimpleXMLElement(object): """Simple XML manipulation (simil PHP)""" def __init__(self, text=None, elements=None, document=None, namespace=None, prefix=None, namespaces_map={}, jetty=False): """ :param namespaces_map: How to map our namespace prefix to that given by the client; {prefix: received_prefix} """ self.__namespaces_map = namespaces_map _rx = "|".join(namespaces_map.keys()) # {'external': 'ext', 'model': 'mod'} -> 'external|model' self.__ns_rx = re.compile(r"^(%s):.*$" % _rx) # And now we build an expression ^(external|model):.*$ # to find prefixes in all xml nodes i.e.: 1 # and later change that to 1 self.__ns = namespace self.__prefix = prefix self.__jetty = jetty # special list support if text is not None: try: self.__document = xml.dom.minidom.parseString(text) except: log.error(text) raise self.__elements = [self.__document.documentElement] else: self.__elements = elements self.__document = document def add_child(self, name, text=None, ns=True): """Adding a child tag to a node""" if not ns or self.__ns is False: ##log.debug('adding %s without namespace', name) element = self.__document.createElement(name) else: ##log.debug('adding %s ns "%s" %s', name, self.__ns, ns) if isinstance(ns, basestring): element = self.__document.createElement(name) if ns: element.setAttribute("xmlns", ns) elif self.__prefix: element = self.__document.createElementNS(self.__ns, "%s:%s" % (self.__prefix, name)) else: element = self.__document.createElementNS(self.__ns, name) # don't append null tags! if text is not None: if isinstance(text, xml.dom.minidom.CDATASection): element.appendChild(self.__document.createCDATASection(text.data)) else: element.appendChild(self.__document.createTextNode(text)) self._element.appendChild(element) return SimpleXMLElement( elements=[element], document=self.__document, namespace=self.__ns, prefix=self.__prefix, jetty=self.__jetty, namespaces_map=self.__namespaces_map ) def __setattr__(self, tag, text): """Add text child tag node (short form)""" if tag.startswith("_"): object.__setattr__(self, tag, text) else: ##log.debug('__setattr__(%s, %s)', tag, text) self.add_child(tag, text) def __delattr__(self, tag): """Remove a child tag (non recursive!)""" elements = [__element for __element in self._element.childNodes if __element.nodeType == __element.ELEMENT_NODE] for element in elements: self._element.removeChild(element) def add_comment(self, data): """Add an xml comment to this child""" comment = self.__document.createComment(data) self._element.appendChild(comment) def as_xml(self, filename=None, pretty=False): """Return the XML representation of the document""" if not pretty: return self.__document.toxml('UTF-8') else: return self.__document.toprettyxml(encoding='UTF-8') if sys.version > '3': def __repr__(self): """Return the XML representation of this tag""" return self._element.toxml() else: def __repr__(self): """Return the XML representation of this tag""" # NOTE: do not use self.as_xml('UTF-8') as it returns the whole xml doc return self._element.toxml('UTF-8') def get_name(self): """Return the tag name of this node""" return self._element.tagName def get_local_name(self): """Return the tag local name (prefix:name) of this node""" return self._element.localName def get_prefix(self): """Return the namespace prefix of this node""" return self._element.prefix def get_namespace_uri(self, ns): """Return the namespace uri for a prefix""" element = self._element while element is not None and element.attributes is not None: try: return element.attributes['xmlns:%s' % ns].value except KeyError: element = element.parentNode def attributes(self): """Return a dict of attributes for this tag""" #TODO: use slice syntax [:]? return self._element.attributes def __getitem__(self, item): """Return xml tag attribute value or a slice of attributes (iter)""" ##log.debug('__getitem__(%s)', item) if isinstance(item, basestring): if self._element.hasAttribute(item): return self._element.attributes[item].value elif isinstance(item, slice): # return a list with name:values return list(self._element.attributes.items())[item] else: # return element by index (position) element = self.__elements[item] return SimpleXMLElement( elements=[element], document=self.__document, namespace=self.__ns, prefix=self.__prefix, jetty=self.__jetty, namespaces_map=self.__namespaces_map ) def add_attribute(self, name, value): """Set an attribute value from a string""" self._element.setAttribute(name, value) def __setitem__(self, item, value): """Set an attribute value""" if isinstance(item, basestring): self.add_attribute(item, value) elif isinstance(item, slice): # set multiple attributes at once for k, v in value.items(): self.add_attribute(k, v) def __delitem__(self, item): "Remove an attribute" self._element.removeAttribute(item) def __call__(self, tag=None, ns=None, children=False, root=False, error=True, ): """Search (even in child nodes) and return a child tag by name""" try: if root: # return entire document return SimpleXMLElement( elements=[self.__document.documentElement], document=self.__document, namespace=self.__ns, prefix=self.__prefix, jetty=self.__jetty, namespaces_map=self.__namespaces_map ) if tag is None: # if no name given, iterate over siblings (same level) return self.__iter__() if children: # future: filter children? by ns? return self.children() elements = None if isinstance(tag, int): # return tag by index elements = [self.__elements[tag]] if ns and not elements: for ns_uri in isinstance(ns, (tuple, list)) and ns or (ns, ): ##log.debug('searching %s by ns=%s', tag, ns_uri) elements = self._element.getElementsByTagNameNS(ns_uri, tag) if elements: break if self.__ns and not elements: ##log.debug('searching %s by ns=%s', tag, self.__ns) elements = self._element.getElementsByTagNameNS(self.__ns, tag) if not elements: ##log.debug('searching %s', tag) elements = self._element.getElementsByTagName(tag) if not elements: ##log.debug(self._element.toxml()) if error: raise AttributeError("No elements found") else: return return SimpleXMLElement( elements=elements, document=self.__document, namespace=self.__ns, prefix=self.__prefix, jetty=self.__jetty, namespaces_map=self.__namespaces_map) except AttributeError as e: raise AttributeError("Tag not found: %s (%s)" % (tag, e)) def __getattr__(self, tag): """Shortcut for __call__""" return self.__call__(tag) def __iter__(self): """Iterate over xml tags at this level""" try: for __element in self.__elements: yield SimpleXMLElement( elements=[__element], document=self.__document, namespace=self.__ns, prefix=self.__prefix, jetty=self.__jetty, namespaces_map=self.__namespaces_map) except: raise def __dir__(self): """List xml children tags names""" return [node.tagName for node in self._element.childNodes if node.nodeType != node.TEXT_NODE] def children(self): """Return xml children tags element""" elements = [__element for __element in self._element.childNodes if __element.nodeType == __element.ELEMENT_NODE] if not elements: return None #raise IndexError("Tag %s has no children" % self._element.tagName) return SimpleXMLElement( elements=elements, document=self.__document, namespace=self.__ns, prefix=self.__prefix, jetty=self.__jetty, namespaces_map=self.__namespaces_map ) def __len__(self): """Return element count""" return len(self.__elements) def __contains__(self, item): """Search for a tag name in this element or child nodes""" return self._element.getElementsByTagName(item) def __unicode__(self): """Returns the unicode text nodes of the current element""" rc = '' for node in self._element.childNodes: if node.nodeType == node.TEXT_NODE or node.nodeType == node.CDATA_SECTION_NODE: rc = rc + node.data return rc if sys.version > '3': __str__ = __unicode__ else: def __str__(self): return self.__unicode__().encode('utf-8') def __int__(self): """Returns the integer value of the current element""" return int(self.__str__()) def __float__(self): """Returns the float value of the current element""" try: return float(self.__str__()) except: raise IndexError(self._element.toxml()) _element = property(lambda self: self.__elements[0]) def unmarshall(self, types, strict=True): #import pdb; pdb.set_trace() """Convert to python values the current serialized xml element""" # types is a dict of {tag name: convertion function} # strict=False to use default type conversion if not specified # example: types={'p': {'a': int,'b': int}, 'c': [{'d':str}]} # expected xml:

12

holachau # returnde value: {'p': {'a':1,'b':2}, `'c':[{'d':'hola'},{'d':'chau'}]} d = {} for node in self(): name = str(node.get_local_name()) ref_name_type = None # handle multirefs: href="#id0" if 'href' in node.attributes().keys(): href = node['href'][1:] for ref_node in self(root=True)("multiRef"): if ref_node['id'] == href: node = ref_node ref_name_type = ref_node['xsi:type'].split(":")[1] break try: if isinstance(types, dict): fn = types[name] # custom array only in the response (not defined in the WSDL): # fn = None elif None in types: # , return the SimpleXMLElement # TODO: check position of None if inside fn = None elif strict: raise TypeError("Tag: %s invalid (type not found)" % (name,)) else: # if not strict, use default type conversion fn = str if isinstance(fn, list): # append to existing list (if any) - unnested dict arrays - value = d.setdefault(name, []) children = node.children() # TODO: check if this was really needed (get first child only) ##if len(fn[0]) == 1 and children: ## children = children() if fn and not isinstance(fn[0], dict): # simple arrays [] for child in (children or []): tmp_dict = child.unmarshall(fn[0], strict) value.extend(tmp_dict.values()) elif (self.__jetty and len(fn[0]) > 1): # Jetty array style support [{k, v}] for parent in node: tmp_dict = {} # unmarshall each value & mix for child in (node.children() or []): tmp_dict.update(child.unmarshall(fn[0], strict)) value.append(tmp_dict) else: # .Net / Java for child in (children or []): value.append(child.unmarshall(fn[0], strict)) elif isinstance(fn, tuple): value = [] _d = {} children = node.children() as_dict = len(fn) == 1 and isinstance(fn[0], dict) for child in (children and children() or []): # Readability counts if as_dict: _d.update(child.unmarshall(fn[0], strict)) # Merging pairs else: value.append(child.unmarshall(fn[0], strict)) if as_dict: value.append(_d) if name in d: _tmp = list(d[name]) _tmp.extend(value) value = tuple(_tmp) else: value = tuple(value) elif isinstance(fn, dict): ##if ref_name_type is not None: ## fn = fn[ref_name_type] children = node.children() value = children and children.unmarshall(fn, strict) else: if fn is None: # xsd:anyType not unmarshalled value = node elif unicode(node) or (fn == str and unicode(node) != ''): try: # get special deserialization function (if any) fn = TYPE_UNMARSHAL_FN.get(fn, fn) if fn == str: # always return an unicode object: # (avoid encoding errors in py<3!) value = unicode(node) else: value = fn(unicode(node)) except (ValueError, TypeError) as e: raise ValueError("Tag: %s: %s" % (name, e)) else: value = None d[name] = value return d def _update_ns(self, name): """Replace the defined namespace alias with tohse used by the client.""" pref = self.__ns_rx.search(name) if pref: pref = pref.groups()[0] try: name = name.replace(pref, self.__namespaces_map[pref]) except KeyError: log.warning('Unknown namespace alias %s' % name) return name def marshall(self, name, value, add_child=True, add_comments=False, ns=False, add_children_ns=True): """Analyze python value and add the serialized XML element using tag name""" # Change node name to that used by a client name = self._update_ns(name) if isinstance(value, dict): # serialize dict (value) # for the first parent node, use the document target namespace # (ns==True) or use the namespace string uri if passed (elements) child = add_child and self.add_child(name, ns=ns) or self for k, v in value.items(): if not add_children_ns: ns = False elif hasattr(value, 'namespaces'): # for children, use the wsdl element target namespace: ns = value.namespaces.get(k) else: # simple type ns = None child.marshall(k, v, add_comments=add_comments, ns=ns) elif isinstance(value, tuple): # serialize tuple (value) child = add_child and self.add_child(name, ns=ns) or self if not add_children_ns: ns = False for k, v in value: getattr(self, name).marshall(k, v, add_comments=add_comments, ns=ns) elif isinstance(value, list): # serialize lists child = self.add_child(name, ns=ns) if not add_children_ns: ns = False if add_comments: child.add_comment("Repetitive array of:") for t in value: child.marshall(name, t, False, add_comments=add_comments, ns=ns) elif isinstance(value, (xml.dom.minidom.CDATASection, basestring)): # do not convert strings or unicodes self.add_child(name, value, ns=ns) elif value is None: # sent a empty tag? self.add_child(name, ns=ns) elif value in TYPE_MAP.keys(): # add commented placeholders for simple tipes (for examples/help only) child = self.add_child(name, ns=ns) child.add_comment(TYPE_MAP[value]) else: # the rest of object types are converted to string # get special serialization function (if any) fn = TYPE_MARSHAL_FN.get(type(value), str) self.add_child(name, fn(value), ns=ns) def import_node(self, other): x = self.__document.importNode(other._element, True) # deep copy self._element.appendChild(x) def write_c14n(self, output=None, exclusive=True): "Generate the canonical version of the XML node" from . import c14n xml = c14n.Canonicalize(self._element, output, unsuppressedPrefixes=[] if exclusive else None) return xml pysimplesoap-1.16/pysimplesoap/transport.py000066400000000000000000000231641245630617100213340ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Pythonic simple SOAP Client transport""" import logging import sys try: import urllib2 from cookielib import CookieJar except ImportError: from urllib import request as urllib2 from http.cookiejar import CookieJar from . import __author__, __copyright__, __license__, __version__, TIMEOUT from .simplexml import SimpleXMLElement, TYPE_MAP, Struct log = logging.getLogger(__name__) # # Socket wrapper to enable socket.TCP_NODELAY - this greatly speeds up transactions in Linux # WARNING: this will modify the standard library socket module, use with care! # TODO: implement this as a transport faciliy # (to pass options directly to httplib2 or pycurl) # be aware of metaclasses and socks.py (SocksiPy) used by httplib2 if False: import socket realsocket = socket.socket def socketwrap(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0): sockobj = realsocket(family, type, proto) if type == socket.SOCK_STREAM: sockobj.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) return sockobj socket.socket = socketwrap # # We store metadata about what available transport mechanisms we have available. # _http_connectors = {} # libname: classimpl mapping _http_facilities = {} # functionalitylabel: [sequence of libname] mapping class TransportBase: @classmethod def supports_feature(cls, feature_name): return cls._wrapper_name in _http_facilities[feature_name] # # httplib2 support. # try: import httplib2 if sys.version > '3' and httplib2.__version__ <= "0.7.7": import http.client # httplib2 workaround: check_hostname needs a SSL context with either # CERT_OPTIONAL or CERT_REQUIRED # see https://code.google.com/p/httplib2/issues/detail?id=173 orig__init__ = http.client.HTTPSConnection.__init__ def fixer(self, host, port, key_file, cert_file, timeout, context, check_hostname, *args, **kwargs): chk = kwargs.get('disable_ssl_certificate_validation', True) ^ True orig__init__(self, host, port=port, key_file=key_file, cert_file=cert_file, timeout=timeout, context=context, check_hostname=chk) http.client.HTTPSConnection.__init__ = fixer except ImportError: TIMEOUT = None # timeout not supported by urllib2 pass else: class Httplib2Transport(httplib2.Http, TransportBase): _wrapper_version = "httplib2 %s" % httplib2.__version__ _wrapper_name = 'httplib2' def __init__(self, timeout, proxy=None, cacert=None, sessions=False): # httplib2.debuglevel=4 kwargs = {} if proxy: import socks kwargs['proxy_info'] = httplib2.ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, **proxy) log.info("using proxy %s" % proxy) # set optional parameters according supported httplib2 version if httplib2.__version__ >= '0.3.0': kwargs['timeout'] = timeout if httplib2.__version__ >= '0.7.0': kwargs['disable_ssl_certificate_validation'] = cacert is None kwargs['ca_certs'] = cacert httplib2.Http.__init__(self, **kwargs) _http_connectors['httplib2'] = Httplib2Transport _http_facilities.setdefault('proxy', []).append('httplib2') _http_facilities.setdefault('cacert', []).append('httplib2') import inspect if 'timeout' in inspect.getargspec(httplib2.Http.__init__)[0]: _http_facilities.setdefault('timeout', []).append('httplib2') # # urllib2 support. # class urllib2Transport(TransportBase): _wrapper_version = "urllib2 %s" % urllib2.__version__ _wrapper_name = 'urllib2' def __init__(self, timeout=None, proxy=None, cacert=None, sessions=False): if (timeout is not None) and not self.supports_feature('timeout'): raise RuntimeError('timeout is not supported with urllib2 transport') if proxy: raise RuntimeError('proxy is not supported with urllib2 transport') if cacert: raise RuntimeError('cacert is not support with urllib2 transport') self.request_opener = urllib2.urlopen if sessions: opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(CookieJar())) self.request_opener = opener.open self._timeout = timeout def request(self, url, method="GET", body=None, headers={}): req = urllib2.Request(url, body, headers) try: f = self.request_opener(req, timeout=self._timeout) return f.info(), f.read() except urllib2.HTTPError as f: if f.code != 500: raise return f.info(), f.read() _http_connectors['urllib2'] = urllib2Transport _http_facilities.setdefault('sessions', []).append('urllib2') import sys if sys.version_info >= (2, 6): _http_facilities.setdefault('timeout', []).append('urllib2') del sys # # pycurl support. # experimental: pycurl seems faster + better proxy support (NTLM) + ssl features # try: import pycurl except ImportError: pass else: try: from cStringIO import StringIO except ImportError: try: from StringIO import StringIO except ImportError: from io import StringIO class pycurlTransport(TransportBase): _wrapper_version = pycurl.version _wrapper_name = 'pycurl' def __init__(self, timeout, proxy=None, cacert=None, sessions=False): self.timeout = timeout self.proxy = proxy or {} self.cacert = cacert def request(self, url, method, body, headers): c = pycurl.Curl() c.setopt(pycurl.URL, url) if 'proxy_host' in self.proxy: c.setopt(pycurl.PROXY, self.proxy['proxy_host']) if 'proxy_port' in self.proxy: c.setopt(pycurl.PROXYPORT, self.proxy['proxy_port']) if 'proxy_user' in self.proxy: c.setopt(pycurl.PROXYUSERPWD, "%(proxy_user)s:%(proxy_pass)s" % self.proxy) self.buf = StringIO() c.setopt(pycurl.WRITEFUNCTION, self.buf.write) #c.setopt(pycurl.READFUNCTION, self.read) #self.body = StringIO(body) #c.setopt(pycurl.HEADERFUNCTION, self.header) if self.cacert: c.setopt(c.CAINFO, self.cacert) c.setopt(pycurl.SSL_VERIFYPEER, self.cacert and 1 or 0) c.setopt(pycurl.SSL_VERIFYHOST, self.cacert and 2 or 0) c.setopt(pycurl.CONNECTTIMEOUT, self.timeout / 6) c.setopt(pycurl.TIMEOUT, self.timeout) if method == 'POST': c.setopt(pycurl.POST, 1) c.setopt(pycurl.POSTFIELDS, body) if headers: hdrs = ['%s: %s' % (k, v) for k, v in headers.items()] log.debug(hdrs) c.setopt(pycurl.HTTPHEADER, hdrs) c.perform() c.close() return {}, self.buf.getvalue() _http_connectors['pycurl'] = pycurlTransport _http_facilities.setdefault('proxy', []).append('pycurl') _http_facilities.setdefault('cacert', []).append('pycurl') _http_facilities.setdefault('timeout', []).append('pycurl') class DummyTransport: """Testing class to load a xml response""" def __init__(self, xml_response): self.xml_response = xml_response def request(self, location, method, body, headers): log.debug("%s %s", method, location) log.debug(headers) log.debug(body) return {}, self.xml_response def get_http_wrapper(library=None, features=[]): # If we are asked for a specific library, return it. if library is not None: try: return _http_connectors[library] except KeyError: raise RuntimeError('%s transport is not available' % (library,)) # If we haven't been asked for a specific feature either, then just return our favourite # implementation. if not features: return _http_connectors.get('httplib2', _http_connectors['urllib2']) # If we are asked for a connector which supports the given features, then we will # try that. current_candidates = _http_connectors.keys() new_candidates = [] for feature in features: for candidate in current_candidates: if candidate in _http_facilities.get(feature, []): new_candidates.append(candidate) current_candidates = new_candidates new_candidates = [] # Return the first candidate in the list. try: candidate_name = current_candidates[0] except IndexError: raise RuntimeError("no transport available which supports these features: %s" % (features,)) else: return _http_connectors[candidate_name] def set_http_wrapper(library=None, features=[]): """Set a suitable HTTP connection wrapper.""" global Http Http = get_http_wrapper(library, features) return Http def get_Http(): """Return current transport class""" global Http return Http # define the default HTTP connection class (it can be changed at runtime!): set_http_wrapper() pysimplesoap-1.16/pysimplesoap/wsse.py000066400000000000000000000205361245630617100202610ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Pythonic simple SOAP Client plugins for WebService Security extensions""" from __future__ import unicode_literals import sys if sys.version > '3': basestring = unicode = str import datetime from decimal import Decimal import os import logging import hashlib import warnings from . import __author__, __copyright__, __license__, __version__ from .simplexml import SimpleXMLElement # Namespaces: WSSE_URI = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' WSU_URI = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" XMLDSIG_URI = "http://www.w3.org/2000/09/xmldsig#" X509v3_URI = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" Base64Binary_URI = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" class UsernameToken: "WebService Security extension to add a basic credentials to xml request" def __init__(self, username="", password=""): self.token = { 'wsse:UsernameToken': { 'wsse:Username': username, 'wsse:Password': password, } } def preprocess(self, client, request, method, args, kwargs, headers, soap_uri): "Add basic credentials to outgoing message" # always extract WS Security header and send it header = request('Header', ns=soap_uri, ) k = 'wsse:Security' # for backward compatibility, use header if given: if k in headers: self.token = headers[k] # convert the token to xml header.marshall(k, self.token, ns=False, add_children_ns=False) header(k)['xmlns:wsse'] = WSSE_URI # def postprocess(self, client, response, method, args, kwargs, headers, soap_uri): "Analyze incoming credentials" # TODO: add some password validation callback? pass BIN_TOKEN_TMPL = """ %(certificate)s %(signed_info)s %(signature_value)s """ class BinaryTokenSignature: "WebService Security extension to add a basic signature to xml request" def __init__(self, certificate="", private_key="", password=None, cacert=None): # read the X509v3 certificate (PEM) self.certificate = ''.join([line for line in open(certificate) if not line.startswith("---")]) self.private_key = private_key self.password = password self.cacert = cacert def preprocess(self, client, request, method, args, kwargs, headers, soap_uri): "Sign the outgoing SOAP request" # get xml elements: body = request('Body', ns=soap_uri, ) header = request('Header', ns=soap_uri, ) # prepare body xml attributes to be signed (reference) body['wsu:Id'] = "id-14" body['xmlns:wsu'] = WSU_URI # workaround: copy namespaces so lxml can parse the xml to be signed for attr, value in request[:]: if attr.startswith("xmlns"): body[attr] = value # use the internal tag xml representation (not the full xml document) ref_xml = repr(body) # sign using RSA-SHA1 (XML Security) from . import xmlsec vars = xmlsec.rsa_sign(ref_xml, "#id-14", self.private_key, self.password) vars['certificate'] = self.certificate # generate the xml (filling the placeholders) wsse = SimpleXMLElement(BIN_TOKEN_TMPL % vars) header.import_node(wsse) def postprocess(self, client, response, method, args, kwargs, headers, soap_uri): "Verify the signature of the incoming response" from . import xmlsec # get xml elements: body = response('Body', ns=soap_uri, ) header = response('Header', ns=soap_uri, ) wsse = header("Security", ns=WSSE_URI) cert = wsse("BinarySecurityToken", ns=WSSE_URI) # check that the cert (binary token) is coming in the correct format: self.__check(cert["EncodingType"], Base64Binary_URI) self.__check(cert["ValueType"], X509v3_URI) # extract the certificate (in DER to avoid new line & padding issues!) cert_der = str(cert).decode("base64") public_key = xmlsec.x509_extract_rsa_public_key(cert_der, binary=True) # validate the certificate using the certification authority: if not self.cacert: warnings.warn("No CA provided, WSSE not validating certificate") elif not xmlsec.x509_verify(self.cacert, cert_der, binary=True): raise RuntimeError("WSSE certificate validation failed") # check body xml attributes was signed correctly (reference) self.__check(body['xmlns:wsu'], WSU_URI) ref_uri = body['wsu:Id'] signature = wsse("Signature", ns=XMLDSIG_URI) signed_info = signature("SignedInfo") signature_value = signature("SignatureValue") # TODO: these sanity checks should be moved to xmlsec? self.__check(signed_info("Reference")['URI'], "#" + ref_uri) self.__check(signed_info("SignatureMethod")['Algorithm'], XMLDSIG_URI + "rsa-sha1") self.__check(signed_info("Reference")("DigestMethod")['Algorithm'], XMLDSIG_URI + "sha1") # TODO: check KeyInfo uses the correct SecurityTokenReference # workaround: copy namespaces so lxml can parse the xml to be signed for attr, value in response[:]: if attr.startswith("xmlns"): body[attr] = value # use the internal tag xml representation (not the full xml document) ref_xml = xmlsec.canonicalize(repr(body)) # verify the signed hash computed_hash = xmlsec.sha1_hash_digest(ref_xml) digest_value = str(signed_info("Reference")("DigestValue")) if computed_hash != digest_value: raise RuntimeError("WSSE SHA1 hash digests mismatch") # workaround: prepare the signed info (assure the parent ns is present) signed_info['xmlns'] = XMLDSIG_URI xml = repr(signed_info) # verify the signature using RSA-SHA1 (XML Security) ok = xmlsec.rsa_verify(xml, str(signature_value), public_key) if not ok: raise RuntimeError("WSSE RSA-SHA1 signature verification failed") # TODO: remove any unsigned part from the xml? def __check(self, value, expected, msg="WSSE sanity check failed"): if value != expected: raise RuntimeError(msg) pysimplesoap-1.16/pysimplesoap/xmlsec.py000066400000000000000000000200031245630617100205600ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Pythonic XML Security Library implementation""" import base64 import hashlib import os from cStringIO import StringIO from M2Crypto import BIO, EVP, RSA, X509, m2 # if lxml is not installed, use c14n.py native implementation try: import lxml.etree except ImportError: lxml = None # Features: # * Uses M2Crypto and lxml (libxml2) but it is independent from libxmlsec1 # * Sign, Verify, Encrypt & Decrypt XML documents # Enveloping templates ("by reference": signature is parent): SIGN_REF_TMPL = """ %(digest_value)s """ SIGNED_TMPL = """ %(signed_info)s %(signature_value)s %(key_info)s %(ref_xml)s """ # Enveloped templates (signature is child, the reference is the root object): SIGN_ENV_TMPL = """ %(digest_value)s """ SIGNATURE_TMPL = """ %(signed_info)s %(signature_value)s %(key_info)s """ KEY_INFO_RSA_TMPL = """ %(modulus)s %(exponent)s """ KEY_INFO_X509_TMPL = """ %(issuer_name)s %(serial_number)s """ def canonicalize(xml, c14n_exc=True): "Return the canonical (c14n) form of the xml document for hashing" # UTF8, normalization of line feeds/spaces, quoting, attribute ordering... output = StringIO() if lxml is not None: # use faster libxml2 / lxml canonicalization function if available et = lxml.etree.parse(StringIO(xml)) et.write_c14n(output, exclusive=c14n_exc) else: # use pure-python implementation: c14n.py (avoid recursive import) from .simplexml import SimpleXMLElement SimpleXMLElement(xml).write_c14n(output, exclusive=c14n_exc) return output.getvalue() def sha1_hash_digest(payload): "Create a SHA1 hash and return the base64 string" return base64.b64encode(hashlib.sha1(payload).digest()) def rsa_sign(xml, ref_uri, private_key, password=None, cert=None, c14n_exc=True, sign_template=SIGN_REF_TMPL, key_info_template=KEY_INFO_RSA_TMPL): "Sign an XML document usign RSA (templates: enveloped -ref- or enveloping)" # normalize the referenced xml (to compute the SHA1 hash) ref_xml = canonicalize(xml, c14n_exc) # create the signed xml normalized (with the referenced uri and hash value) signed_info = sign_template % {'ref_uri': ref_uri, 'digest_value': sha1_hash_digest(ref_xml)} signed_info = canonicalize(signed_info, c14n_exc) # Sign the SHA1 digest of the signed xml using RSA cipher pkey = RSA.load_key(private_key, lambda *args, **kwargs: password) signature = pkey.sign(hashlib.sha1(signed_info).digest()) # build the mapping (placeholders) to create the final xml signed message return { 'ref_xml': ref_xml, 'ref_uri': ref_uri, 'signed_info': signed_info, 'signature_value': base64.b64encode(signature), 'key_info': key_info(pkey, cert, key_info_template), } def rsa_verify(xml, signature, key, c14n_exc=True): "Verify a XML document signature usign RSA-SHA1, return True if valid" # load the public key (from buffer or filename) if key.startswith("-----BEGIN PUBLIC KEY-----"): bio = BIO.MemoryBuffer(key) rsa = RSA.load_pub_key_bio(bio) else: rsa = RSA.load_pub_key(certificate) # create the digital envelope pubkey = EVP.PKey() pubkey.assign_rsa(rsa) # do the cryptographic validation (using the default sha1 hash digest) pubkey.reset_context(md='sha1') pubkey.verify_init() # normalize and feed the signed xml to be verified pubkey.verify_update(canonicalize(xml, c14n_exc)) ret = pubkey.verify_final(base64.b64decode(signature)) return ret == 1 def key_info(pkey, cert, key_info_template): "Convert private key (PEM) to XML Signature format (RSAKeyValue/X509Data)" exponent = base64.b64encode(pkey.e[4:]) modulus = m2.bn_to_hex(m2.mpi_to_bn(pkey.n)).decode("hex").encode("base64") x509 = x509_parse_cert(cert) if cert else None return key_info_template % { 'modulus': modulus, 'exponent': exponent, 'issuer_name': x509.get_issuer().as_text() if x509 else "", 'serial_number': x509.get_serial_number() if x509 else "", } # Miscellaneous certificate utility functions: def x509_parse_cert(cert, binary=False): "Create a X509 certificate from binary DER, plain text PEM or filename" if binary: bio = BIO.MemoryBuffer(cert) x509 = X509.load_cert_bio(bio, X509.FORMAT_DER) elif cert.startswith("-----BEGIN CERTIFICATE-----"): bio = BIO.MemoryBuffer(cert) x509 = X509.load_cert_bio(bio, X509.FORMAT_PEM) else: x509 = X509.load_cert(cert, 1) return x509 def x509_extract_rsa_public_key(cert, binary=False): "Return the public key (PEM format) from a X509 certificate" x509 = x509_parse_cert(cert, binary) return x509.get_pubkey().get_rsa().as_pem() def x509_verify(cacert, cert, binary=False): "Validate the certificate's authenticity using a certification authority" ca = x509_parse_cert(cacert) crt = x509_parse_cert(cert, binary) return crt.verify(ca.get_pubkey()) if __name__ == "__main__": # basic test of enveloping signature (the reference is a part of the xml) sample_xml = """data""" print canonicalize(sample_xml) vars = rsa_sign(sample_xml, '#object', "no_encriptada.key", "password") print SIGNED_TMPL % vars # basic test of enveloped signature (the reference is the document itself) sample_xml = """data%s""" vars = rsa_sign(sample_xml % "", '', "no_encriptada.key", "password", sign_template=SIGN_ENV_TMPL, c14n_exc=False) print sample_xml % (SIGNATURE_TMPL % vars) # basic signature verification: public_key = x509_extract_rsa_public_key(open("zunimercado.crt").read()) assert rsa_verify(vars['signed_info'], vars['signature_value'], public_key, c14n_exc=False) pysimplesoap-1.16/samples/000077500000000000000000000000001245630617100156375ustar00rootroot00000000000000pysimplesoap-1.16/samples/wsaa_py.py000066400000000000000000000126211245630617100176560ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: latin-1 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. "Example to get Autorization Ticket (WSAA webservice) - Aduana Paraguay" # Inspired on wsaa-client.php from DvSHyS/DiOPIN/AFIP (Argentina) - 13-apr-07 __author__ = "Mariano Reingart (reingart@gmail.com)" __copyright__ = "Copyright (C) 2008-2011 Mariano Reingart" __license__ = "GPL 3.0" __version__ = "2.08a" import hashlib, datetime, email, os, sys, time, traceback from pysimplesoap.client import SoapClient, SimpleXMLElement from pysimplesoap import xmlsec from M2Crypto import BIO, Rand, SMIME, SSL # openssl binding # Constants CERT = "pbbox.crt" # X.509 certificate (in PEM format) PRIVATEKEY = "pbbox.key" # RSA private key (in PEM format) PASSPHRASE = "xxxxxxx" # private key password (if any) # Webservice URL (test: homologacion): WSDL = {'test': "https://secure.aduana.gov.py/test/wsaa/server?wsdl", 'prod': "https://secure.aduana.gov.py/wsaaserver/Server?wsdl"} # Remote webserver certificate validation, needed for "secure channel" spec CACERT = None # WSAA CA Cert (Autoridades de Confiaza) DEFAULT_TTL = 60*60*5 # five hours TIMEOUT = 60 # 60 seconds for http connection timeout DEBUG = True def create_tra(service=None, ttl=2400, cert=None): "Create a Access Request Ticket (TRA)" # Base TRA squeleton (Ticket de Requerimiento de Acceso) tra = SimpleXMLElement( '' '' '') tra.add_child('header') # get the source from the certificate subject, ie "CN=empresa, O=dna, C=py" if cert: crt = xmlsec.x509_parse_cert(cert) tra.header.add_child('source', crt.get_subject().as_text()) tra.header.add_child('destination', 'C=py, O=dna, OU=sofia, CN=wsaatest') d = int(time.mktime(datetime.datetime.now().timetuple())) tra.header.add_child('uniqueId', str(d)) date = lambda ts: datetime.datetime.fromtimestamp(ts).isoformat() tra.header.add_child('generationTime', str(date(d-ttl))) tra.header.add_child('expirationTime', str(date(d+ttl))) tra.add_child('service', service) return tra.as_xml() def sign_tra(tra,cert=CERT,privatekey=PRIVATEKEY,passphrase=""): "Sign using PKCS#7 the TRA and return CMS (trimming SMIME headers)" # Sign the text (tra) using m2crypto (openssl bindings for python) buf = BIO.MemoryBuffer(tra) # create the buffer from the file #Rand.load_file('randpool.dat', -1) # seed the PRNG s = SMIME.SMIME() # instantiate the SMIME # support encription passwords (for private key, optional) callback = lambda *args, **kwarg: passphrase # load the private key and certificate s.load_key(privatekey, cert, callback) # (frmo file) p7 = s.sign(buf,0) # Sign the buffer out = BIO.MemoryBuffer() # Instantiathe the output buffer s.write(out, p7) # Generate p7 in mail format #Rand.save_file('randpool.dat') # Store the PRNG's state # extract the message body (signed part) msg = email.message_from_string(out.read()) for part in msg.walk(): filename = part.get_filename() if filename == "smime.p7m": # is the signed part? return part.get_payload(decode=False) # return the CMS def call_wsaa(cms, wsdl=WSDL, proxy=None, cache=None, wrapper="", trace=False): "Call the RPC method with the CMS to get the authorization ticket (TA)" # create the webservice client client = SoapClient( location = wsdl[:-5], #location, use wsdl, cache = cache, #proxy = parse_proxy(proxy), #cacert = cacert, timeout = TIMEOUT, ns = "ejb", # TODO: find a better method to not include ns prefix in children: # (wsdl parse should detect qualification instead of server dialect) soap_server = "jetty", namespace = "http://ejb.server.wsaa.dna.gov.py/", soap_ns = "soapenv", trace = trace) # fix the wrong location (192.4.1.39:8180 in the WDSL) ##ws = client.services['WsaaServerBeanService'] ##location = ws['ports']['WsaaServerBeanPort']['location'] ##location = location.replace("192.4.1.39:8180", "secure.aduana.gov.py") ##ws['ports']['WsaaServerBeanPort']['location'] = wsdl[:-5] #location # call the remote method try: results = client.loginCms(arg0=str(cms)) except: # save sent and received messages for debugging: open("request.xml", "w").write(client.xml_request) open("response.xml", "w").write(client.xml_response) raise # extract the result: ta = results['return'].encode("utf-8") return ta if __name__=="__main__": tra = create_tra(service="test", ttl=DEFAULT_TTL, cert=CERT) print tra cms = sign_tra(tra, CERT, PRIVATEKEY) ta = call_wsaa(cms, WSDL['test'], trace=True) print ta pysimplesoap-1.16/setup.py000077500000000000000000000013201245630617100157040ustar00rootroot00000000000000#!/usr/bin/env python import setuptools from distutils.core import setup try: import py2exe from nsis import build_installer except: build_installer = None from pysimplesoap import __version__, __author__, __author_email__, __license__ # in the transition, register both: for name in ('soap2py', 'PySimpleSOAP'): setup( name=name, version=__version__, description='Python simple and lightweight SOAP Library', author=__author__, author_email=__author_email__, url='http://code.google.com/p/pysimplesoap', packages=['pysimplesoap'], license=__license__, # console=['client.py'], cmdclass={"py2exe": build_installer}, ) pysimplesoap-1.16/tests/000077500000000000000000000000001245630617100153355ustar00rootroot00000000000000pysimplesoap-1.16/tests/__init__.py000066400000000000000000000000001245630617100174340ustar00rootroot00000000000000pysimplesoap-1.16/tests/afip_test.py000066400000000000000000000123341245630617100176700ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Argentina AFIP (IRS) Electronic Invoice & Currency Exchange Control""" from decimal import Decimal import os import unittest from pysimplesoap.client import SimpleXMLElement, SoapClient, SoapFault, parse_proxy, set_http_wrapper from .dummy_utils import DummyHTTP, TEST_DIR WSDLs = [ "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl", "https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl", "https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL", "https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL", "https://fwshomo.afip.gov.ar/wsmtxca/services/MTXCAService?wsdl", "https://serviciosjava.afip.gob.ar/wsmtxca/services/MTXCAService?wsdl", "https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL", "https://servicios1.afip.gov.ar/wsfexv1/service.asmx?WSDL", ] wrapper = None cache = "./cache" proxy_dict = None cacert = None class TestIssues(unittest.TestCase): def test_wsaa_exception(self): """Test WSAA for SoapFault""" WSDL = "https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl" client = SoapClient(wsdl=WSDL, ns="web") try: resultado = client.loginCms('31867063') except SoapFault as e: self.assertEqual(e.faultcode, 'ns1:cms.bad') try: resultado = client.loginCms(in0='31867063') except SoapFault as e: self.assertEqual(e.faultcode, 'ns1:cms.bad') def test_wsfev1_dummy(self): """Test Argentina AFIP Electronic Invoice WSFEv1 dummy method""" client = SoapClient( wsdl="https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL", cache=None ) result = client.FEDummy()['FEDummyResult'] self.assertEqual(result['AppServer'], "OK") self.assertEqual(result['DbServer'], "OK") self.assertEqual(result['AuthServer'], "OK") def test_wsfexv1_dummy(self): """Test Argentina AFIP Electronic Invoice WSFEXv1 dummy method""" client = SoapClient( wsdl="https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL", cache=None ) result = client.FEXDummy()['FEXDummyResult'] self.assertEqual(result['AppServer'], "OK") self.assertEqual(result['DbServer'], "OK") self.assertEqual(result['AuthServer'], "OK") def test_wsbfe_dummy(self): """Test Argentina AFIP Electronic Invoice WSBFE dummy method""" client = SoapClient( wsdl="https://wswhomo.afip.gov.ar/wsbfe/service.asmx?WSDL", cache=None ) result = client.BFEDummy()['BFEDummyResult'] self.assertEqual(result['AppServer'], "OK") self.assertEqual(result['DbServer'], "OK") self.assertEqual(result['AuthServer'], "OK") def test_wsmtxca_dummy(self): """Test Argentina AFIP Electronic Invoice WSMTXCA dummy method""" client = SoapClient( wsdl="https://fwshomo.afip.gov.ar/wsmtxca/services/MTXCAService?wsdl", cache=None, ns='ser' ) result = client.dummy() self.assertEqual(result['appserver'], "OK") self.assertEqual(result['dbserver'], "OK") self.assertEqual(result['authserver'], "OK") def test_wsfexv1_getcmp(self): """Test Argentina AFIP Electronic Invoice WSFEXv1 GetCMP method""" # create the proxy and parse the WSDL client = SoapClient( wsdl="https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL", cache=None ) # load saved xml xml = open(os.path.join(TEST_DIR, "wsfexv1_getcmp.xml")).read() client.http = DummyHTTP(xml) # call RPC ret = client.FEXGetCMP( Auth={'Token': "", 'Sign': "", 'Cuit': "0"}, Cmp={ 'Cbte_tipo': "19", 'Punto_vta': "3", 'Cbte_nro': "38", }) # analyze result result = ret['FEXGetCMPResult'] self.assertEqual(result['FEXErr']['ErrCode'], 0) self.assertEqual(result['FEXErr']['ErrMsg'], 'OK') self.assertEqual(result['FEXEvents']['EventCode'], 0) resultget = result['FEXResultGet'] self.assertEqual(resultget['Obs'], None) self.assertEqual(resultget['Cae'], '61473001385110') self.assertEqual(resultget['Fch_venc_Cae'], '20111202') self.assertEqual(resultget['Fecha_cbte'], '20111122') self.assertEqual(resultget['Punto_vta'], 3) self.assertEqual(resultget['Resultado'], "A") self.assertEqual(resultget['Cbte_nro'], 38) self.assertEqual(resultget['Imp_total'], Decimal('130.21')) self.assertEqual(resultget['Cbte_tipo'], 19) pysimplesoap-1.16/tests/cfdi_mx_test.py000066400000000000000000000055711245630617100203670ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Mexico SAT (IRS) Electronic Invoice (Comprobantes Fiscales Digitales)""" from decimal import Decimal import os import unittest from pysimplesoap.client import SoapClient, SoapFault import sys if sys.version > '3': basestring = str long = int class TestCFDI(unittest.TestCase): def test_obtener_token(self): # Concetarse al webservice (en producción, ver cache y otros parametros): WSDL = "http://pruebas.ecodex.com.mx:2044/ServicioSeguridad.svc?wsdl" client = SoapClient(wsdl=WSDL, ns="ns0", soap_ns="soapenv") # llamo al método remoto: retval = client.ObtenerToken(RFC="AAA010101AAA", TransaccionID=1234) # muestro los resultados: self.assertIsInstance(retval['Token'], basestring) self.assertIsInstance(retval['TransaccionID'], long) def test_cancela(self): # Concetarse al webservice (en producción, ver cache y otros parametros): WSDL = "https://pruebas.ecodex.com.mx:2045/ServicioCancelacion.svc?wsdl" client = SoapClient(wsdl=WSDL, ns="cfdi", soap_ns="soapenv") try: r = client.CancelaMultiple( ListaCancelar=[{"guid": "abcdabcd-abcd-abcd-acbd-abcdabcdabcd"}], RFC="AAA010101AAA", Token="62cb344df85acab90c3a68174ed5e452b3c50b2a", TransaccionID=1234) except SoapFault as sf: self.assertIn("El Token no es valido o ya expiro", str(sf.faultstring)) ##for res in r['Resultado']: ## rc = res['ResultadoCancelacion'] ## print rc['UUID'], rc['Estatus'] ## print res['TransaccionID'] def test_timbrado(self): # this tests "infinite recursion" issues # Concetarse al webservice (en producción, ver cache y otros parametros): WSDL = "https://digitalinvoicecfdi.com.mx/WS_WSDI/DigitalInvoice.WebServices.WSDI.Timbrado.svc?wsdl" #WSDL = "federico.wsdl" client = SoapClient(wsdl=WSDL, ns="ns0", soap_ns="soapenv") # llamo al método remoto: try: retval = client.TimbrarTest(comprobanteBytesZipped="1234") except SoapFault as sf: self.assertIn("verifying security for the message", str(sf.faultstring)) # muestro los resultados: ##print retval['TimbrarTestResult'] pysimplesoap-1.16/tests/client_test.py000066400000000000000000000224441245630617100202320ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import unicode_literals import sys if sys.version > '3': long = int if __name__ == "__main__": import sys if '--web2py' in sys.argv: # test local sample webservice exposed by web2py from client import SoapClient if not '--wsdl' in sys.argv: client = SoapClient( location="http://127.0.0.1:8000/webservices/sample/call/soap", action='http://127.0.0.1:8000/webservices/sample/call/soap', # SOAPAction namespace="http://127.0.0.1:8000/webservices/sample/call/soap", soap_ns='soap', ns=False, exceptions=True) else: client = SoapClient(wsdl="http://127.0.0.1:8000/webservices/sample/call/soap?WSDL") response = client.Dummy() print('dummy', response) response = client.Echo(value='hola') print('echo', repr(response)) response = client.AddIntegers(a=1, b=2) if not '--wsdl' in sys.argv: result = response.AddResult # manully convert returned type print(int(result)) else: result = response['AddResult'] print(result, type(result), "auto-unmarshalled") if '--raw' in sys.argv: # raw (unmarshalled parameter) local sample webservice exposed by web2py from client import SoapClient client = SoapClient( location="http://127.0.0.1:8000/webservices/sample/call/soap", action='http://127.0.0.1:8000/webservices/sample/call/soap', # SOAPAction namespace="http://127.0.0.1:8000/webservices/sample/call/soap", soap_ns='soap', ns=False) params = SimpleXMLElement("""32""") # manully convert returned type response = client.call('AddIntegers', params) result = response.AddResult print(int(result)) # manully convert returned type if '--ctg' in sys.argv: # test AFIP Agriculture webservice client = SoapClient( location="https://fwshomo.afip.gov.ar/wsctg/services/CTGService", action='http://impl.service.wsctg.afip.gov.ar/CTGService/', # SOAPAction namespace="http://impl.service.wsctg.afip.gov.ar/CTGService/", ns=True) response = client.dummy() result = response.dummyResponse print(str(result.appserver)) print(str(result.dbserver)) print(str(result.authserver)) if '--wsfe' in sys.argv: # Demo & Test (AFIP Electronic Invoice): ta_string = open("TA.xml").read() # read access ticket (wsaa.py) ta = SimpleXMLElement(ta_string) token = str(ta.credentials.token) sign = str(ta.credentials.sign) cuit = long(20267565393) id = 1234 cbte = 199 client = SoapClient( location="https://wswhomo.afip.gov.ar/wsfe/service.asmx", action='http://ar.gov.afip.dif.facturaelectronica/', # SOAPAction namespace="http://ar.gov.afip.dif.facturaelectronica/") results = client.FERecuperaQTYRequest( argAuth={"Token": token, "Sign": sign, "cuit": long(cuit)} ) if int(results.FERecuperaQTYRequestResult.RError.percode) != 0: print("Percode: %s" % results.FERecuperaQTYRequestResult.RError.percode) print("MSGerror: %s" % results.FERecuperaQTYRequestResult.RError.perrmsg) else: print(int(results.FERecuperaQTYRequestResult.qty.value)) if '--feriados' in sys.argv: # Demo & Test: Argentina Holidays (Ministerio del Interior): # this webservice seems disabled from datetime import datetime, timedelta client = SoapClient( location="http://webservices.mininterior.gov.ar/Feriados/Service.svc", action='http://tempuri.org/IMyService/', # SOAPAction namespace="http://tempuri.org/FeriadoDS.xsd") dt1 = datetime.today() - timedelta(days=60) dt2 = datetime.today() + timedelta(days=60) feriadosXML = client.FeriadosEntreFechasas_xml(dt1=dt1.isoformat(), dt2=dt2.isoformat()) print(feriadosXML) if '--wsdl-parse' in sys.argv: if '--proxy' in sys.argv: proxy = parse_proxy("localhost:8000") else: proxy = None if '--wrapper' in sys.argv: set_http_wrapper("pycurl") client = SoapClient(proxy=proxy) # Test PySimpleSOAP WSDL ##client.wsdl("file:C:/test.wsdl", debug=True) # Test Java Axis WSDL: client.wsdl_parse('https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl', debug=True) # Test .NET 2.0 WSDL: client.wsdl_parse('https://wswhomo.afip.gov.ar/wsfe/service.asmx?WSDL', debug=True) client.wsdl_parse('https://wswhomo.afip.gov.ar/wsfex/service.asmx?WSDL', debug=True) client.wsdl_parse('https://testdia.afip.gov.ar/Dia/Ws/wDigDepFiel/wDigDepFiel.asmx?WSDL', debug=True) client.services = client.wsdl_parse('https://wswhomo.afip.gov.ar/wsfexv1/service.asmx?WSDL', debug=True) print(client.help("FEXGetCMP")) # Test JBoss WSDL: client.wsdl_parse('https://fwshomo.afip.gov.ar/wsctg/services/CTGService?wsdl', debug=True) client.wsdl_parse('https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl', debug=True) if '--wsdl-client' in sys.argv: import time t0 = time.time() for i in range(100): print(i) client = SoapClient(wsdl='https://wswhomo.afip.gov.ar/wsfex/service.asmx?WSDL', cache="cache") #results = client.FEXDummy() #print(results['FEXDummyResult']['AppServer']) #print(results['FEXDummyResult']['DbServer']) #print(results['FEXDummyResult']['AuthServer']) t1 = time.time() print("Total time", t1 - t0) if '--wsdl-client' in sys.argv: ta_string = open("TA.xml").read() # read access ticket (wsaa.py) ta = SimpleXMLElement(ta_string) token = str(ta.credentials.token) sign = str(ta.credentials.sign) response = client.FEXGetCMP( Auth={"Token": token, "Sign": sign, "Cuit": 20267565393}, Cmp={"Tipo_cbte": 19, "Punto_vta": 1, "Cbte_nro": 1}) result = response['FEXGetCMPResult'] #if False: print(result) # ? if 'FEXErr' in result: print("FEXError:", result['FEXErr']['ErrCode'], result['FEXErr']['ErrCode']) cbt = result['FEXResultGet'] print(cbt['Cae']) FEX_event = result['FEXEvents'] print(FEX_event['EventCode'], FEX_event['EventMsg']) if '--wsdl-ctg' in sys.argv: client = SoapClient(wsdl='https://fwshomo.afip.gov.ar/wsctg/services/CTGService?wsdl', ns="ctg") results = client.dummy() print(results) print(results['DummyResponse']['appserver']) print(results['DummyResponse']['dbserver']) print(results['DummyResponse']['authserver']) ta_string = open("TA.xml").read() # read access ticket (wsaa.py) ta = SimpleXMLElement(ta_string) token = str(ta.credentials.token) sign = str(ta.credentials.sign) print(client.help("obtenerProvincias")) response = client.obtenerProvincias(auth={"token": token, "sign": sign, "cuitRepresentado": 20267565393}) print("response=", response) for ret in response: print(ret['return']['codigoProvincia'], ret['return']['descripcionProvincia'].encode("latin1")) prueba = dict( numeroCartaDePorte=512345678, codigoEspecie=23, cuitRemitenteComercial=20267565393, cuitDestino=20267565393, cuitDestinatario=20267565393, codigoLocalidadOrigen=3058, codigoLocalidadDestino=3059, codigoCosecha='0910', pesoNetoCarga=1000, cantHoras=1, patenteVehiculo='CZO985', cuitTransportista=20267565393, numeroCTG="43816783", transaccion='10000001681', observaciones='', ) response = client.solicitarCTG( auth={"token": token, "sign": sign, "cuitRepresentado": 20267565393}, solicitarCTGRequest=prueba) print(response['return']['numeroCTG']) if '--libtest' in sys.argv: import time results = {} for lib in 'httplib2', 'urllib2', 'pycurl': print("testing library", lib) set_http_wrapper(lib) print(Http._wrapper_version) for proxy in None, parse_proxy("localhost:8000"): print("proxy", proxy) try: client = SoapClient(wsdl='https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL', cache="cache", proxy=proxy) t0 = time.time() print("starting...",) for i in range(20): print(i,) client.FEDummy() t1 = time.time() result = t1 - t0 except Exception as e: result = "Failed: %s" % e print("Total time", result) results.setdefault(lib, {})[proxy and 'proxy' or 'direct'] = result print("\nResults:") for k, v in list(results.items()): for k2, v2 in list(v.items()): print(k, k2, v2) pysimplesoap-1.16/tests/data/000077500000000000000000000000001245630617100162465ustar00rootroot00000000000000pysimplesoap-1.16/tests/data/teca_server_wsdl.xml000066400000000000000000000315621245630617100223320ustar00rootroot00000000000000 pysimplesoap-1.16/tests/data/vco.wsdl000066400000000000000000000766141245630617100177460ustar00rootroot00000000000000 pysimplesoap-1.16/tests/dummy_utils.py000066400000000000000000000002021245630617100202540ustar00rootroot00000000000000import os TEST_DIR = os.path.dirname(os.path.abspath(__file__)) from pysimplesoap.transport import DummyTransport as DummyHTTP pysimplesoap-1.16/tests/issues_test.py000066400000000000000000000726261245630617100202760ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime import unittest import httplib2 import socket from xml.parsers.expat import ExpatError from pysimplesoap.client import SoapClient, SimpleXMLElement, SoapFault from .dummy_utils import DummyHTTP, TEST_DIR import sys if sys.version > '3': basestring = str unicode = str class TestIssues(unittest.TestCase): def test_issue19(self): """Test xsd namespace found under schema elementes""" client = SoapClient( wsdl='http://uat.destin8.co.uk:80/ChiefEDI/ChiefEDI?wsdl' ) def test_issue34(self): """Test soap_server SoapClient constructor parameter""" client = SoapClient( wsdl="http://eklima.met.no/metdata/MetDataService?WSDL", soap_server="oracle", cache=None ) ##print(client.help("getStationsProperties")) ##print(client.help("getValidLanguages")) # fix bad wsdl: server returns "getValidLanguagesResponse" # instead of "getValidLanguages12Response" met_data = client.services['MetDataService']['ports']['MetDataServicePort'] languages = met_data['operations']['getValidLanguages'] output = languages['output']['getValidLanguages13Response'] languages['output'] = {'getValidLanguagesResponse': output} lang = client.getValidLanguages() self.assertEqual(lang, {'return': ['no', 'en', 'ny']}) def test_issue35_raw(self): client = SoapClient( location="http://wennekers.epcc.ed.ac.uk:8080" "/axis/services/MetadataCatalogue", action="" ) response = client.call( "doEnsembleURIQuery", ("queryFormat", "Xpath"), ("queryString", "/markovChain"), ("startIndex", 0), ("maxResults", -1) ) self.assertEqual(str(response.statusCode), "MDC_INVALID_REQUEST") #print(str(response.queryTime)) self.assertEqual(int(response.totalResults), 0) self.assertEqual(int(response.startIndex), 0) self.assertEqual(int(response.numberOfResults), 0) for result in response.results: str(result) def test_issue35_wsdl(self): """Test positional parameters, multiRefs and axis messages""" client = SoapClient( wsdl="http://wennekers.epcc.ed.ac.uk:8080/axis/services/MetadataCatalogue?WSDL", soap_server="axis" ) response = client.doEnsembleURIQuery( queryFormat="Xpath", queryString="/markovChain", startIndex=0, maxResults=-1 ) ret = response['doEnsembleURIQueryReturn'] self.assertEqual(ret['statusCode'], "MDC_INVALID_REQUEST") self.assertEqual(ret['totalResults'], 0) self.assertEqual(ret['startIndex'], 0) self.assertEqual(ret['numberOfResults'], 0) def test_issue8_raw(self): """Test europa.eu tax service (namespace - raw call)""" client = SoapClient( location="http://ec.europa.eu/taxation_customs/vies/services/checkVatService", action='', # SOAPAction namespace="urn:ec.europa.eu:taxud:vies:services:checkVat:types" ) vat = 'IE6388047V' code = vat[:2] number = vat[2:] res = client.checkVat(countryCode=code, vatNumber=number) self.assertEqual(unicode(res('countryCode')), "IE") self.assertEqual(unicode(res('vatNumber')), "6388047V") self.assertEqual(unicode(res('name')), "GOOGLE IRELAND LIMITED") self.assertEqual(unicode(res('address')), "1ST & 2ND FLOOR ,GORDON HOUSE ," "BARROW STREET ,DUBLIN 4") def test_issue8_wsdl(self): """Test europa.eu tax service (namespace - wsdl call)""" URL='http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl' client = SoapClient(wsdl=URL) # check the correct target namespace: self.assertEqual(client.namespace, "urn:ec.europa.eu:taxud:vies:services:checkVat:types") # call the webservice to check everything else: vat = 'BE0897290877' code = vat[:2] number = vat[2:] res = client.checkVat(countryCode=code, vatNumber=number) # check returned values: self.assertEqual(res['name'], "SPRL B2CK") self.assertEqual(res['address'], "RUE DE ROTTERDAM 4 B21\n" "4000 LIEGE") ## NOTE: Missing file "ups.wsdl" ##def test_ups(self): ## "Test UPS tracking service" ## WSDL = "file:ups.wsdl" ## client = SoapClient(wsdl=WSDL, ns="web") ## print(client.help("ProcessTrack")) def test_issue43(self): client = SoapClient( wsdl="https://api.clarizen.com/v1.0/Clarizen.svc" ) client.help("Login") client.help("Logout") client.help("Query") client.help("Metadata") client.help("Execute") def test_issue44(self): """Test namespace""" client = SoapClient(wsdl="https://api.clarizen.com/v1.0/Clarizen.svc") try: response = client.Login(userName="foo",password="bar") except Exception as e: self.assertEquals(e.faultcode, 's:InvalidUserNameOrPassword') def test_issue46(self): """Example for sending an arbitrary header using SimpleXMLElement""" # fake connection (just to test xml_request): client = SoapClient( location="https://localhost:666/", namespace='http://localhost/api' ) # Using WSDL, the equivalent is: # client['MyTestHeader'] = {'username': 'test', 'password': 'test'} headers = SimpleXMLElement("") my_test_header = headers.add_child("MyTestHeader") my_test_header['xmlns'] = "service" my_test_header.marshall('username', 'test') my_test_header.marshall('password', 'password') try: client.methodname(headers=headers) except: open("issue46.xml", "wb").write(client.xml_request) self.assert_('' 'test' 'password' '' in client.xml_request.decode(), "header not in request!") def test_issue47_wsdl(self): """Separate Header message WSDL (carizen)""" client = SoapClient(wsdl="https://api.clarizen.com/v1.0/Clarizen.svc") session = client['Session'] = {'ID': '1234'} try: client.Logout() except: open("issue47_wsdl.xml", "wb").write(client.xml_request) self.assert_('' '1234' '' in client.xml_request.decode(), "Session header not in request!") def test_issue47_raw(self): """Same example (clarizen), with raw headers (no wsdl)!""" client = SoapClient( location="https://api.clarizen.com/v1.0/Clarizen.svc", namespace='http://clarizen.com/api' ) headers = SimpleXMLElement("", namespace="http://clarizen.com/api", prefix="ns1") session = headers.add_child("Session") session['xmlns'] = "http://clarizen.com/api" session.marshall('ID', '1234') client.location = "https://api.clarizen.com/v1.0/Clarizen.svc" client.action = "http://clarizen.com/api/IClarizen/Logout" try: client.call("Logout", headers=headers) except: open("issue47_raw.xml", "wb").write(client.xml_request) self.assert_('' '1234' '' in client.xml_request.decode(), "Session header not in request!") def test_issue49(self): """Test netsuite wsdl""" client = SoapClient(wsdl="https://webservices.netsuite.com/wsdl/v2011_2_0/netsuite.wsdl") try: response = client.login(passport=dict(email="joe@example.com", password="secret", account='hello', role={'name': 'joe'})) except Exception as e: # It returns "This document you requested has moved temporarily." pass def test_issue57(self): """Test SalesForce wsdl""" # open the attached sfdc_enterprise_v20.wsdl to the issue in googlecode client = SoapClient(wsdl="https://pysimplesoap.googlecode.com/issues/attachment?aid=570000001&name=sfdc_enterprise_v20.wsdl&token=bD6VTXMx8p4GJQHGhlQI1ISorSA%3A1399085346613") try: response = client.login(username="john", password="doe") except Exception as e: # It returns "This document you requested has moved temporarily." self.assertEqual(e.faultcode, 'INVALID_LOGIN') def test_issue60(self): """Verify unmarshalling of custom xsi:type="SOAPENC:Array" """ wsdl_url = 'http://peopleask.ooz.ie/soap.wsdl' client = SoapClient(wsdl=wsdl_url, soap_server="unknown", trace=False) questions = client.GetQuestionsAbout(query="money") self.assertIsInstance(questions, list) for question in questions: self.assertIsNotNone(question) self.assertNotEqual(question, "") def test_issue66(self): """Verify marshaled requests can be sent with no children""" # fake connection (just to test xml_request): client = SoapClient( location="https://localhost:666/", namespace='http://localhost/api' ) request = SimpleXMLElement("") try: client.call('ChildlessRequest', request) except: open("issue66.xml", "wb").write(client.xml_request) self.assert_('' in client.xml_request.decode(), " not in request!") def test_issue69(self): """Boolean value not converted correctly during marshall""" span = SimpleXMLElement('foo') span.marshall('value', True) d = {'span': {'name': str, 'value': bool}} e = {'span': {'name': 'foo', 'value': True}} self.assertEqual(span.unmarshall(d), e) def test_issue78(self): """Example for sending an arbitrary header using SimpleXMLElement and WSDL""" # fake connection (just to test xml_request): client = SoapClient( wsdl='http://dczorgwelzijn-test.qmark.nl/qmwise4/qmwise.asmx?wsdl' ) # Using WSDL, the easier form is but this doesn't allow for namespaces to be used. # If the server requires these (buggy server?) the dictionary method won't work # and marshall will not marshall 'ns:username' style keys # client['MyTestHeader'] = {'username': 'test', 'password': 'test'} namespace = 'http://questionmark.com/QMWISe/' ns = 'qmw' header = SimpleXMLElement('', namespace=namespace, prefix=ns) security = header.add_child("Security") security['xmlns:qmw'] = namespace security.marshall('ClientID', 'NAME', ns=ns) security.marshall('Checksum', 'PASSWORD', ns=ns) client['Security'] = security try: client.GetParticipantList() except: #open("issue78.xml", "wb").write(client.xml_request) #print(client.xml_request) header = '' \ '' \ 'NAME' \ 'PASSWORD' \ '' \ '' xml = SimpleXMLElement(client.xml_request) self.assertEquals(str(xml.ClientID), "NAME") self.assertEquals(str(xml.Checksum), "PASSWORD") def test_issue80(self): """Test services.conzoom.eu/addit/ wsdl""" client = SoapClient(wsdl="http://services.conzoom.eu/addit/AddItService.svc?wsdl") client.help("GetValues") def atest_issue80(self): """Test Issue in sending a webservice request with soap12""" client = SoapClient(wsdl="http://testserver:7007/testapp/services/testService?wsdl", soap_ns='soap12', trace=False, soap_server='oracle') try: result = client.hasRole(userId='test123', role='testview') except httplib2.ServerNotFoundError: pass def test_issue89(self): """Setting attributes for request tag.""" # fake connection (just to test xml_request): client = SoapClient( location="https://localhost:666/", namespace='http://localhost/api' ) request = SimpleXMLElement( """3""" ) # manually make request msg try: client.call('test', request) except: open("issue89.xml", "wb").write(client.xml_request) self.assert_('' in client.xml_request.decode(), "attribute not in request!") def test_issue93(self): """Response with and """ # attached sample response to the ticket: xml = """ http://smbsaas/websitepanel /enterpriseserver/AddPackageResponseurn:uuid:af841fc e-4607-4e4b-910e-252d1f1857fburn:uuid:fea15079-42 57-424b-8da7-8c9a29ec52cehttp://schemas.xmlsoap.org/ws/2 004/08/addressing/role/anonymous798 """ xml = xml.replace("\n","").replace("\r","") # parse the wsdl attached to the ticket client = SoapClient(wsdl="https://pysimplesoap.googlecode.com/issues/attachment?aid=930004001&name=wsdl.txt&token=MIcIgTXvGmzpfFgLM-noYLehzwU%3A1399083528469", trace=False) # put the sample response (no call to the real webservice is made...) client.http = DummyHTTP(xml) result = client.AddPackage(657, 33, 'Services', 'Comment', 1, datetime.datetime.now()) # check unmarshalled results: self.assertEquals(result['AddPackageResult']['Result'], 798) # the schema is also returned as a SimpleXMLElement object (unmarshalled), get the xml: self.assertEquals(repr(result['AddPackageResult']['ExceedingQuotas']['schema']['element']), '') # the any is also returned as a SimpleXMLElement object (unmarshalled) self.assertEquals(str(result['AddPackageResult']['ExceedingQuotas']['diffgram']), '') def test_issue94(self): """Test wather forecast web service.""" client = SoapClient(wsdl='http://www.restfulwebservices.net/wcf/WeatherForecastService.svc?wsdl') ret = client.GetCitiesByCountry('korea') for d in ret['GetCitiesByCountryResult']: #print d['string'] self.assertEquals(d.keys()[0], 'string') self.assertEquals(len(ret['GetCitiesByCountryResult']), 53) self.assertEquals(len(ret['GetCitiesByCountryResult'][0]), 1) self.assertEquals(ret['GetCitiesByCountryResult'][0]['string'], 'KWANGJU') def test_issue101(self): """automatic relative import support""" client = SoapClient(wsdl="https://raw.github.com/vadimcomanescu/vmwarephp/master/library/Vmwarephp/Wsdl/vimService.wsdl") try: client.Login(parameters={'userName': 'username', 'password': 'password'}) except IOError: pass try: client.Logout() except IOError: pass def test_issue104(self): """SoapClient did not build all arguments for Marketo.""" method = 'getLead' args = {'leadKey': {'keyType': 'IDNUM', 'keyValue': '1'}} # fake connection (just to test xml_request): client = SoapClient(wsdl='http://app.marketo.com/soap/mktows/2_1?WSDL') input = client.get_operation(method)['input'] params = ('paramsGetLead', [('leadKey', {'keyType': 'IDNUM', 'keyValue': '1'})]) self.assertEqual(params, client.wsdl_call_get_params(method, input, [args], {})) self.assertEqual(params, client.wsdl_call_get_params(method, input, [], dict(leadKey=args['leadKey']))) def test_issue109(self): """Test multirefs and string arrays""" WSDL = 'http://usqcd.jlab.org/mdc-service/services/ILDGMDCService?wsdl' client = SoapClient(wsdl=WSDL,soap_server='axis') response = client.doEnsembleURIQuery("Xpath", "/markovChain", 0, -1) ret = response['doEnsembleURIQueryReturn'] self.assertIsInstance(ret['numberOfResults'], int) self.assertIsInstance(ret['results'], list) self.assertIsInstance(ret['results'][0], basestring) self.assertIsInstance(ret['queryTime'], basestring) self.assertEqual(ret['statusCode'], "MDC_SUCCESS") def test_issue109bis(self): """Test string arrays not defined in the wsdl (but sent in the response)""" WSDL = 'http://globe-meta.ifh.de:8080/axis/services/ILDG_MDC?wsdl' client = SoapClient(wsdl=WSDL,soap_server='axis') response = client.doEnsembleURIQuery("Xpath", "/markovChain", 0, -1) ret = response['doEnsembleURIQueryReturn'] self.assertIsInstance(ret['numberOfResults'], int) self.assertIsInstance(ret['results'], list) self.assertIsInstance(ret['results'][0], basestring) def test_issue113(self): """Test target namespace in wsdl import""" WSDL = "https://test.paymentgate.ru/testpayment/webservices/merchant-ws?wsdl" client = SoapClient(wsdl=WSDL) try: client.getOrderStatusExtended(order={'merchantOrderNumber':'1'}) except SoapFault as sf: # ignore exception caused by missing credentials sent in this test: if sf.faultstring != "An error was discovered processing the header": raise # verify the correct namespace: xml = SimpleXMLElement(client.xml_request) ns_uri = xml.getOrderStatusExtended['xmlns'] self.assertEqual(ns_uri, "http://engine.paymentgate.ru/webservices/merchant") def test_issue105(self): """Test target namespace in wsdl (conflicting import)""" WSDL = "https://api.dc2.computing.cloud.it/WsEndUser/v2.4/WsEndUser.svc?wsdl" client = SoapClient(wsdl=WSDL) try: client.SetEnqueueServerStop(serverId=37) except SoapFault as sf: # ignore exception caused by missing credentials sent in this test: if sf.faultstring != "An error occurred when verifying security for the message.": raise # verify the correct namespace: xml = SimpleXMLElement(client.xml_request) ns_uri = xml.SetEnqueueServerStop['xmlns'] self.assertEqual(ns_uri, "https://api.computing.cloud.it/WsEndUser") def test_issue114(self): """Test no schema in wsdl (Lotus-Domino)""" WSDL = "https://pysimplesoap.googlecode.com/issues/attachment?aid=1140000000&name=WebRequest.xml&token=QVf8DlJ1qmKRH8LAbU4eSe2Ban0%3A1399084258723" # WSDL= "file:WebRequest.xml" try: client = SoapClient(wsdl=WSDL, soap_server="axis") #print client.help("CREATEREQUEST") ret = client.CREATEREQUEST(LOGIN="hello", REQUESTTYPE=1, REQUESTCONTENT="test") except ExpatError: # the service seems to be expecting basic auth pass except SoapFault as sf: # todo: check as service is returning DOM failure # verify the correct namespace: xml = SimpleXMLElement(client.xml_request) ns_uri = xml.CREATEREQUEST['xmlns'] self.assertEqual(ns_uri, "http://tps.ru") def test_issue116(self): """Test string conversion and encoding of a SoapFault exception""" exception = SoapFault('000', 'fault stríng') exception_string = str(exception) self.assertTrue(isinstance(exception_string, str)) if sys.version < '3': self.assertEqual(exception_string, '000: fault strng') else: self.assertEqual(exception_string, '000: fault stríng') def test_issue122(self): """Test multiple separate messages in input header""" APIURL = "https://ecommercetest.collector.se/v3.0/InvoiceServiceV31.svc?singleWsdl" client = SoapClient(wsdl=APIURL) # set headers (first two were not correctly handled client['Username'] = 'user' client['Password'] = 'pass' client['ClientIpAddress'] = '127.0.0.1' variables = { "CountryCode": "SE", "RegNo": "1234567890", } expected_xml = ("" "user" "pass" "127.0.0.1" "") try: response = client.GetAddress(**variables) except SoapFault: self.assertIn(expected_xml, client.xml_request) def test_issue123(self): """Basic test for WSDL lacking service tag """ wsdl = "http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl" client = SoapClient(wsdl=wsdl) # this could cause infinite recursion (TODO: investigate) #client.help("CreateUsers") #client.help("GetServices") # this is not a real webservice (just specification) catch HTTP error try: client.GetServices(IncludeCapability=True) except Exception as e: self.assertEqual(str(e), "RelativeURIError: Only absolute URIs are allowed. uri = ") def test_issue127(self): """Test relative schema locations in imports""" client = SoapClient(wsdl = 'https://eisoukr.musala.com:9443/IrmInboundMediationWeb/sca/MTPLPolicyWSExport/WEB-INF/wsdl/wsdl/IrmInboundMediation_MTPLPolicyWSExport.wsdl') try: resp = client.voidMTPLPolicy() except Exception as e: self.assertIn('InvalidSecurity', e.faultcode) def test_issue128(self): "" client = SoapClient( wsdl = "https://apiapac.lumesse-talenthub.com/HRIS/SOAP/Candidate?WSDL", location = "https://apiapac.lumesse-talenthub.com/HRIS/SOAP/Candidate?api_key=azhmc6m8sq2gf2jqwywa37g4", ns = True ) # basic test as no test case was provided try: resp = client.getContracts() except: self.assertEqual(client.xml_response, '

Gateway Timeout

') def test_issue129(self): """Test RPC style (axis) messages (including parameter order)""" wsdl_url = 'file:tests/data/teca_server_wsdl.xml' client = SoapClient(wsdl=wsdl_url, soap_server='axis') client.help("contaVolumi") response = client.contaVolumi(user_id=1234, valoreIndice=["IDENTIFIER", ""]) self.assertEqual(response, {'contaVolumiReturn': 0}) def test_issue130(self): """Test complex Array (axis) """ wsdl_url = 'file:tests/data/teca_server_wsdl.xml' client = SoapClient(wsdl=wsdl_url, soap_server='axis', trace=False) #print client.help("find") #response = client.find(25, ['SUBJECT', 'Ethos'], 10, 0) port = client.services[u'WsTecaServerService']['ports']['tecaServer'] op = port['operations']['find'] out = op['output']['findResponse']['findReturn'] # findReturn should be a list of Contenitore self.assertIsInstance(out, list) element = out[0]['Contenitore'] for key in [u'EMail', u'bloccato', u'classe', u'codice', u'creatoDa', u'creatoIl', u'dbName', u'dbPort', u'dbUrl', u'username']: self.assertIn(key, element) # valoriDigitali should be a list of anyType (None) self.assertIsInstance(element[u'valoriDigitali'], list) self.assertIsNone(element[u'valoriDigitali'][0]) def test_issue139(self): """Test MKS wsdl (extension)""" # open the attached Integrity_10_2Service to the issue in googlecode client = SoapClient(wsdl="https://pysimplesoap.googlecode.com/issues/attachment?aid=1390000000&name=Integrity_10_2.wsdl&token=3VG47As2K-EupP9GgotYckgb0Bc%3A1399064656814") #print client.help("getItemsByCustomQuery") try: response = client.getItemsByCustomQuery(arg0={'Username': 'user', 'Password' : 'pass', 'InputField' : 'ID', 'QueryDefinition' : 'query'}) except httplib2.ServerNotFoundError: pass def test_issue141(self): """Test voxone VoxAPI wsdl (ref element)""" import datetime import hmac import hashlib client = SoapClient(wsdl="http://sandbox.voxbone.com/VoxAPI/services/VoxAPI?wsdl", cache=None) client.help("GetPOPList") key = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S:%f000000") password="fwefewfewfew" usertoken={'Username': "oihfweohf", 'Key': key, 'Hash': hmac.new(key, password, digestmod=hashlib.sha1).hexdigest()} try: response = client.GetPOPList(UserToken=usertoken) result = response['GetPOPListResponse'] except SoapFault as sf: # ignore exception caused by missing credentials sent in this test: if sf.faultstring != "Either your username or password is invalid": raise def test_issue143(self): """Test webservice.vso.dunes.ch wsdl (array sub-element)""" wsdl_url = 'file:tests/data/vco.wsdl' try: vcoWS = SoapClient(wsdl=wsdl_url, soap_server="axis", trace=False) workflowInputs = [{'name': 'vmName', 'type': 'string', 'value': 'VMNAME'}] workflowToken = vcoWS.executeWorkflow(workflowId='my_uuid', username="my_user", password="my_password", workflowInputs=workflowInputs) except httplib2.ServerNotFoundError: #import pdb;pdb.set_trace() print vcoWS.xml_response pass def test_issue157(self): """Test WSDL types "top level" import for .NET WCF""" wsdl = "https://sdkstage.microbilt.com/WebServices/Ex/ExStd.svc?wsdl" client = SoapClient(wsdl=wsdl, trace=False) # method call, should not fail, but doesn't return anything useful: client.Map(bureauResponse=1234) # in case of issue, it will throw the following exceptions: # AttributeError: Tag not found: schema (No elements found) # ValueError: Invalid Args Structure if __name__ == '__main__': #unittest.main() suite = unittest.TestSuite() #suite.addTest(TestIssues('test_issue34')) #suite.addTest(TestIssues('test_issue93')) #suite.addTest(TestIssues('test_issue57')) #suite.addTest(TestIssues('test_issue60')) #suite.addTest(TestIssues('test_issue80')) #suite.addTest(TestIssues('test_issue101')) #suite.addTest(TestIssues('test_issue114')) #suite.addTest(TestIssues('test_issue123')) #suite.addTest(TestIssues('test_issue127')) #suite.addTest(TestIssues('test_issue130')) #suite.addTest(TestIssues('test_issue141')) #suite.addTest(TestIssues('test_issue143')) suite.addTest(TestIssues('test_issue157')) unittest.TextTestRunner().run(suite) pysimplesoap-1.16/tests/licencias_test.py000066400000000000000000000064451245630617100207110ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- from __future__ import unicode_literals import os import unittest from pysimplesoap.client import SoapClient, SoapFault from .dummy_utils import DummyHTTP, TEST_DIR class TestIssues(unittest.TestCase): internal = 1 def setUp(self): self.xml = open(os.path.join(TEST_DIR, "licencias.xml")).read() def test_buscar_personas_raw(self): url = "http://www.testgobi.dpi.sfnet/licencias/web/soap.php" client = SoapClient(location=url, ns="web", namespace="http://wwwdesagobi.dpi.sfnet:8080/licencias/web/", action=url) # load dummy response (for testing) client.http = DummyHTTP(self.xml) client['AuthHeaderElement'] = {'username': 'mariano', 'password': 'clave'} response = client.PersonaSearch(persona=(('numero_documento', '99999999'), ('apellido_paterno', ''), ('apellido_materno', ''), ('nombres', ''), )) # the raw response is a SimpleXmlElement object: self.assertEqual(str(response.result.item[0]("xsd:string")[0]), "resultado") self.assertEqual(str(response.result.item[0]("xsd:string")[1]), "true") self.assertEqual(str(response.result.item[1]("xsd:string")[0]), "codigo") self.assertEqual(str(response.result.item[1]("xsd:string")[1]), "WS01-01") self.assertEqual(str(response.result.item[2]("xsd:string")[0]), "mensaje") self.assertEqual(str(response.result.item[2]("xsd:string")[1]), "Se encontraron 1 personas.") self.assertEqual(str(response.result.item[2]("xsd:string")[0]), "mensaje") self.assertEqual(str(response.result.item[2]("xsd:string")[1]), "Se encontraron 1 personas.") self.assertEqual(str(response.result.item[3]("xsd:anyType")[0]), "datos") self.assertEqual(str(response.result.item[3]("xsd:anyType")[1]("ns2:Map").item[0].key), "lic_ps_ext_id") self.assertEqual(str(response.result.item[3]("xsd:anyType")[1]("ns2:Map").item[0].value), "123456") self.assertEqual(str(response.result.item[3]("xsd:anyType")[1]("ns2:Map").item[10].key), "fecha_nacimiento") self.assertEqual(str(response.result.item[3]("xsd:anyType")[1]("ns2:Map").item[10].value), "1985-10-02 00:00:00") def test_buscar_personas_wsdl(self): WSDL = "file://" + os.path.join(TEST_DIR, "licencias.wsdl") client = SoapClient(wsdl=WSDL, ns="web") print(client.help("PersonaSearch")) client['AuthHeaderElement'] = {'username': 'mariano', 'password': 'clave'} client.http = DummyHTTP(self.xml) resultado = client.PersonaSearch(numero_documento='31867063') print(resultado) # each resultado['result'][i]['item'] is xsd:anyType, so it is not unmarshalled # they are SimpleXmlElement (see test_buscar_personas_raw) self.assertEqual(str(resultado['result'][0]['item']('xsd:string')[0]), "resultado") self.assertEqual(str(resultado['result'][1]['item']('xsd:string')[1]), "WS01-01") self.assertEqual(str(resultado['result'][3]['item']('xsd:anyType')[1]("ns2:Map").item[10].value), "1985-10-02 00:00:00") pysimplesoap-1.16/tests/nfp_br_test.py000066400000000000000000000056231245630617100202220ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Brazil - Sao Paulo "Electronic Invoice" (Nota Fiscal Paulista)""" from __future__ import unicode_literals from decimal import Decimal import os import unittest from pysimplesoap.client import SoapClient, SoapFault, SimpleXMLElement import sys if sys.version > '3': basestring = str long = int # Documentation: http://www.nfp.fazenda.sp.gov.br/MIWSCF.pdf WSDL = 'https://www.nfp.fazenda.sp.gov.br/ws/arquivocf.asmx?WSDL' HEADER_XML = """""" # TODO: support attributes in headers / parameters class TestNFP(unittest.TestCase): def test_enviar(self): "Prueba da envio de arquivos de cupons fiscais" # create the client webservice client = SoapClient(wsdl=WSDL, soap_ns="soap12env") # set user login credentials in the soap header: client['Autenticacao'] = SimpleXMLElement(HEADER_XML % ("user","password", "fed_tax_num", 1)) # call the webservice response = client.Enviar(NomeArquivo="file_name", ConteudoArquivo="content", EnvioNormal=True, Observacoes="") self.assertEqual(response['EnviarResult'], '206|CNPJ informado inv\xe1lido') def test_consultar(self): "consulta ao resultado do processamento dos arquivos de cupons fiscai" # create the client webservice client = SoapClient(wsdl=WSDL, soap_ns="soap12env") # set user login credentials in the soap header: client['Autenticacao'] = SimpleXMLElement(HEADER_XML % ("user","password", "fed_tax_num", 1)) # call the webservice response = client.Consultar(Protocolo="") self.assertEqual(response['ConsultarResult'], '999|O protocolo informado n\xe3o \xe9 um n\xfamero v\xe1lido') def test_retificar(self): "Prueba da retifica de arquivos de cupons fiscais" # create the client webservice client = SoapClient(wsdl=WSDL, soap_ns="soap12env") # set user login credentials in the soap header: client['Autenticacao'] = SimpleXMLElement(HEADER_XML % ("user","password", "fed_tax_num", 1)) # call the webservice response = client.Retificar(NomeArquivo="file_name", ConteudoArquivo="content", EnvioNormal=True, Observacoes="") self.assertEqual(response['RetificarResult'], '206|CNPJ informado inv\xe1lido') pysimplesoap-1.16/tests/server_multins_test.py000066400000000000000000000214111245630617100220260ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import logging import unittest from pysimplesoap.server import SoapDispatcher # log = logging.getLogger('pysimplesoap.server') # log.setLevel(logging.DEBUG) # log = logging.getLogger('pysimplesoap.simplexml') # log.setLevel(logging.DEBUG) REQ = """ 791 abc 791000000 x 2 ::260013456789 """ REQ1 = """51072IPLA4872640149438033946812""" SINGLE_NS_RESP = """0desc791000000x20""" MULTI_NS_RESP = """0desc791000000x20""" MULTI_NS_RESP1 = """0desc""" class TestServerMultiNS(unittest.TestCase): def _single_ns_func(self, serviceMsisdn, serviceName, activations=[]): code = 0 desc = 'desc' results = [{ 'items': [ {'msisdn': '791000000'}, {'properties': [{'name': 'x'}, {'value': '2'}]}, {'status': '0'} ]}] ret = { 'activateSubscriptionsReturn': [ {'code': code}, {'description': desc}, ]} ret['activateSubscriptionsReturn'].extend(results) return ret _single_ns_func.returns = {'non-empty-dict': 1} _single_ns_func.args = { 'serviceMsisdn': str, 'serviceName': str, 'activations': [ {'items': { 'msisdn': str, 'status': int, 'parameters': str, 'properties': ({ 'name': str, 'value': str }, ) } } ] } def _updateDeliveryStatus(self, serviceMsisdn, serviceName, messageDeliveryStatuses=[]): code = 0 desc = 'desc' return { 'external:updateDeliveryStatusReturn': [ {'model:code': code}, {'model:description': desc} ] } _updateDeliveryStatus.returns = {'non-empty-dict': 1} _updateDeliveryStatus.args = { 'serviceMsisdn': str, 'serviceName': str, 'messageDeliveryStatuses': [ {'items': { 'msisdn': str, 'status': int, 'deliveryId': str, 'properties': ({ 'name': str, 'value': int }, ) } } ] } def _multi_ns_func(self, serviceMsisdn, serviceName, activations=[]): code = 0 desc = 'desc' results = [{ 'model:items': [ {'model:msisdn': '791000000'}, {'model:properties': [{'model:name': 'x'}, {'model:value': '2'}]}, {'model:status': '0'} ]}] ret = { 'external:activateSubscriptionsReturn': [ {'model:code': code}, {'model:description': desc}, ]} ret['external:activateSubscriptionsReturn'].extend(results) return ret _multi_ns_func.returns = {'non-empty-dict': 1} _multi_ns_func.args = { 'serviceMsisdn': str, 'serviceName': str, 'activations': [ {'items': { 'msisdn': str, 'status': int, 'parameters': str, 'properties': ({ 'name': str, 'value': str }, ) } } ] } def test_single_ns(self): dispatcher = SoapDispatcher( name="MTClientWS", location="http://localhost:8008/ws/MTClientWS", action='http://localhost:8008/ws/MTClientWS', # SOAPAction namespace="http://external.mt.moboperator", prefix="external", documentation='moboperator MTClientWS', ns=True, pretty=False, debug=True) dispatcher.register_function('activateSubscriptions', self._single_ns_func, returns=self._single_ns_func.returns, args=self._single_ns_func.args) # I don't fully know if that is a valid response for a given request, # but I tested it, to be sure that a multi namespace function # doesn't brake anything. self.assertEqual(dispatcher.dispatch(REQ), SINGLE_NS_RESP) def test_multi_ns(self): dispatcher = SoapDispatcher( name="MTClientWS", location="http://localhost:8008/ws/MTClientWS", action='http://localhost:8008/ws/MTClientWS', # SOAPAction namespace="http://external.mt.moboperator", prefix="external", documentation='moboperator MTClientWS', namespaces={ 'external': 'http://external.mt.moboperator', 'model': 'http://model.common.mt.moboperator' }, ns=True, pretty=False, debug=True) dispatcher.register_function('activateSubscriptions', self._multi_ns_func, returns=self._multi_ns_func.returns, args=self._multi_ns_func.args) dispatcher.register_function('updateDeliveryStatus', self._updateDeliveryStatus, returns=self._updateDeliveryStatus.returns, args=self._updateDeliveryStatus.args) self.assertEqual(dispatcher.dispatch(REQ), MULTI_NS_RESP) self.assertEqual(dispatcher.dispatch(REQ1), MULTI_NS_RESP1) if __name__ == '__main__': unittest.main() pysimplesoap-1.16/tests/serverfault_test.py000066400000000000000000000036411245630617100213140ustar00rootroot00000000000000# -*- coding: utf-8 -*- import unittest from pysimplesoap.server import SoapDispatcher, SoapFault from pysimplesoap.simplexml import SimpleXMLElement class ServerSoapFaultTest(unittest.TestCase): def setUp(self): self.dispatcher = SoapDispatcher( 'Test', action='http://localhost:8008/soap', location='http://localhost:8008/soap' ) def divider(a, b): if b == 0: raise SoapFault(faultcode='DivisionByZero', faultstring='Division by zero not allowed', detail='test') return float(a) / b self.dispatcher.register_function( 'Divider', divider, returns={'DivideResult': float}, args={'a': int, 'b': int} ) def test_exception(self): xml = """ 1002 """ response = SimpleXMLElement(self.dispatcher.dispatch(xml)) self.assertEqual(str(response.DivideResult), '50.0') xml = """ 1000 """ response = SimpleXMLElement(self.dispatcher.dispatch(xml)) body = getattr(getattr(response, 'soap:Body'), 'soap:Fault') self.assertIsNotNone(body) self.assertEqual(str(body.faultcode), 'Server.DivisionByZero') self.assertEqual(str(body.faultstring), 'Division by zero not allowed') self.assertEqual(str(body.detail), 'test') pysimplesoap-1.16/tests/simplexmlelement_test.py000066400000000000000000000170531245630617100223400ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import sys import datetime import unittest from xml.dom.minidom import CDATASection from pysimplesoap.simplexml import SimpleXMLElement PY2 = sys.version < '3' class TestSimpleXMLElement(unittest.TestCase): def eq(self, value, expectation, msg=None): if msg is not None: msg += ' %s' % value self.assertEqual(value, expectation, msg) else: self.assertEqual(value, expectation, value) def test_attributes_access(self): span = SimpleXMLElement('pyar11.5') text = "pyar" self.eq(str(span.a), text, 'Access by __getattr__:') self.eq(str(span.a), text, 'Access by __getattr__:') self.eq(str(span('a')), text, 'Access by __call__:') self.eq(str(span.a(0)), text, 'Access by __call__ on attribute:') self.eq(span.a['href'], "python.org.ar", 'Access by __getitem__:') self.eq(int(span.prueba.i), 1, 'Casting to int:') self.eq(float(span.prueba.float), 1.5, 'Casting to float:') def test_to_xml(self): xml = ( '' 'pyar11.5') self.eq(SimpleXMLElement(xml).as_xml(), xml if PY2 else xml.encode('utf-8')) xml = ( '' 'googleyahoohotmail') self.eq(SimpleXMLElement(xml).as_xml(), xml if PY2 else xml.encode('utf-8')) def test_unmarshall_cdata(self): span = SimpleXMLElement(']]>3') d = {'span': {'name': str, 'value': int}} e = {'span': {'name': 'foo', 'value': 3}} self.eq(span.unmarshall(d), e) def test_marshall_cdata(self): span = SimpleXMLElement('') cdata = CDATASection() cdata.data = 'python' span.add_child('a', cdata) xml = '' self.eq(span.as_xml(), xml if PY2 else xml.encode('utf-8')) def test_unmarshall(self): span = SimpleXMLElement('foo3') d = {'span': {'name': str, 'value': int}} e = {'span': {'name': 'foo', 'value': 3}} self.eq(span.unmarshall(d), e) span = SimpleXMLElement('foobar') d = {'span': [{'name': str}]} e = {'span': [{'name': 'foo'}, {'name': 'bar'}]} self.eq(span.unmarshall(d), e) span = SimpleXMLElement('012341043210') d = {'activations': [ {'items': { 'number': str, 'status': int }} ]} e = {'activations': [{'items': {'number': '01234', 'status': 1}}, {'items': {'number': '04321', 'status': 0}}]} self.eq(span.unmarshall(d), e) def test_adv_unmarshall(self): xml = """ 01234 1 foo 3 bar 4 04321 0 """ span = SimpleXMLElement(xml) d = {'activations': [ {'items': { 'number': str, 'status': int, 'properties': ({ 'name': str, 'value': int }, ) }} ]} e = {'activations': [ {'items': {'number': '01234', 'status': 1, 'properties': ({'name': 'foo', 'value': 3}, {'name': 'bar', 'value': 4})}}, {'items': {'number': '04321', 'status': 0}} ]} self.eq(span.unmarshall(d), e) def test_tuple_unmarshall(self): xml = """ abc 1 qwe 2 """ span = SimpleXMLElement(xml) d = {'foo': { 'boo': ({'bar': str, 'baz': int}, ) }} e = {'foo': { 'boo': ( {'bar': 'abc', 'baz': 1}, {'bar': 'qwe', 'baz': 2}, )}} self.eq(span.unmarshall(d), e) def test_basic(self): span = SimpleXMLElement( 'pyar' '11.5') span1 = SimpleXMLElement( 'google' 'yahoohotmail') self.eq([str(a) for a in span1.a()], ['google', 'yahoo', 'hotmail']) span1.add_child('a', 'altavista') span1.b = "ex msn" d = {'href': 'http://www.bing.com/', 'alt': 'Bing'} span1.b[:] = d self.eq(sorted([(k, v) for k, v in span1.b[:]]), sorted(d.items())) xml = ( '' 'googleyahoo' 'hotmailaltavista' 'ex msn') self.eq(span1.as_xml(), xml if PY2 else xml.encode('utf-8')) self.assertTrue('b' in span1) span.import_node(span1) xml = ( '' 'pyar1' '1.5google' 'yahoohotmailaltavista' 'ex msn' '') self.eq(span.as_xml(), xml if PY2 else xml.encode('utf-8')) types = {'when': datetime.datetime} when = datetime.datetime.now() dt = SimpleXMLElement('%s' % when.isoformat()) self.eq(dt.unmarshall(types)['when'], when) def test_repr(self): xml = '123' el = SimpleXMLElement(xml) el_repr = repr(el) self.assertTrue(isinstance(el_repr, str)) self.eq(el_repr, xml) def test_str(self): xml = 'BÀr' # minidom must always parse encoded string in python 2 el = SimpleXMLElement(xml.encode('utf-8') if PY2 else xml) el_str = str(el) self.assertTrue(isinstance(el_str, str)) if PY2: # str is bytestring in py2 self.eq(el_str, 'BÀr'.encode('utf-8')) else: self.eq(el_str, 'BÀr') @unittest.skipUnless(PY2, 'unicode() conversion not present in py3') def test_unicode(self): xml = 'BÀr' el = SimpleXMLElement(xml.encode('utf-8')) el_unicode = unicode(el) self.assertTrue(isinstance(el_unicode, unicode)) self.eq(el_unicode, 'BÀr') if __name__ == '__main__': unittest.main() pysimplesoap-1.16/tests/soapdispatcher_test.py000066400000000000000000000106631245630617100217650ustar00rootroot00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import datetime import unittest from pysimplesoap.server import SoapDispatcher from pysimplesoap.simplexml import Date, Decimal def adder(p, c, dt=None): "Add several values" dt = dt + datetime.timedelta(365) return {'ab': p['a'] + p['b'], 'dd': c[0]['d'] + c[1]['d'], 'dt': dt} def dummy(in0): "Just return input" return in0 def echo(request): "Copy request->response (generic, any type)" return request.value class TestSoapDispatcher(unittest.TestCase): def eq(self, value, expectation, msg=None): if msg is not None: msg += ' %s' % value self.assertEqual(value, expectation, msg) else: self.assertEqual(value, expectation, "%s\n---\n%s" % (value, expectation)) def setUp(self): self.dispatcher = SoapDispatcher( name="PySimpleSoapSample", location="http://localhost:8008/", action='http://localhost:8008/', # SOAPAction namespace="http://example.com/pysimplesoapsamle/", prefix="ns0", documentation='Example soap service using PySimpleSoap', debug=True, ns=True) self.dispatcher.register_function('Adder', adder, returns={'AddResult': {'ab': int, 'dd': str}}, args={'p': {'a': int, 'b': int}, 'dt': Date, 'c': [{'d': Decimal}]}) self.dispatcher.register_function('Dummy', dummy, returns={'out0': str}, args={'in0': str}) self.dispatcher.register_function('Echo', echo) def test_classic_dialect(self): # adder local test (clasic soap dialect) resp = """
5000000.3
3
2011-07-24
""" xml = """

12

5000000.1.2
2010-07-24
""" self.eq(self.dispatcher.dispatch(xml), resp) def test_modern_dialect(self): # adder local test (modern soap dialect, SoapUI) resp = """
15.021
12
1970-07-20
""" xml = """ 93 1969-07-20 10.0015.02 """ self.eq(self.dispatcher.dispatch(xml), resp) def test_echo(self): # echo local test (generic soap service) resp = """Hello world""" xml = """ Hello world """ self.eq(self.dispatcher.dispatch(xml), resp) if __name__ == '__main__': unittest.main() pysimplesoap-1.16/tests/sri_ec_test.py000066400000000000000000000043001245630617100202070ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. """Ecuador S.R.I. Electronic Invoice (Emisión de Documentos Electrónicos)""" from __future__ import unicode_literals from decimal import Decimal import os import unittest from pysimplesoap.client import SoapClient, SoapFault import sys if sys.version > '3': basestring = str long = int # Documentation: http://www.sri.gob.ec/web/10138/145 class TestSRI(unittest.TestCase): def test_validar(self): "Prueba de envío de un comprovante electrónico" WSDL = 'https://celcer.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantes?wsdl' # https://cel.sri.gob.ec/comprobantes-electronicos-ws/RecepcionComprobantes?wsdl client = SoapClient(wsdl=WSDL, ns="ec") ret = client.validarComprobante(xml="cid:1218403525359") self.assertEquals(ret, {'RespuestaRecepcionComprobante': {'comprobantes': [{'comprobante': {'mensajes': [{'mensaje': {'identificador': '35', 'mensaje': 'ARCHIVO NO CUMPLE ESTRUCTURA XML', 'informacionAdicional': 'Content is not allowed in prolog.', 'tipo': 'ERROR'}}], 'claveAcceso': 'N/A'}}], 'estado': 'DEVUELTA'}}) def test_autorizar(self): WSDL = 'https://cel.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantes?wsdl' # https://cel.sri.gob.ec/comprobantes-electronicos-ws/AutorizacionComprobantes?wsdl client = SoapClient(wsdl=WSDL, ns="ec") ret = client.autorizacionComprobante(claveAccesoComprobante="1702201205176001321000110010030001000011234567816") self.assertEquals(ret, {'RespuestaAutorizacionComprobante': {'autorizaciones': [], 'claveAccesoConsultada': '1702201205176001321000110010030001000011234567816', 'numeroComprobantes': '0'}}) pysimplesoap-1.16/tests/suite.py000066400000000000000000000020251245630617100170370ustar00rootroot00000000000000# -*- coding: utf-8 -*- import unittest def add(suite, module): suite.addTest(unittest.TestLoader().loadTestsFromModule(module)) def test(): # TODO: automagicaly import modules test/*_test.py from . import soapdispatcher_test from . import simplexmlelement_test from . import issues_test from . import afip_test from . import server_multins_test # licencias_tests is for internal use, wsdl is not published # from . import licencias_test # from . import trazamed_test from . import cfdi_mx_test from . import sri_ec_test from . import nfp_br_test suite = unittest.TestSuite() add(suite, soapdispatcher_test) add(suite, simplexmlelement_test) add(suite, issues_test) add(suite, afip_test) add(suite, server_multins_test) ##add(suite, licencias_test) ##add(suite, trazamed_test) add(suite, cfdi_mx_test) add(suite, sri_ec_test) add(suite, nfp_br_test) unittest.TextTestRunner(verbosity=2).run(suite) if __name__ == '__main__': test() pysimplesoap-1.16/tests/trazamed_test.py000066400000000000000000000147621245630617100205670ustar00rootroot00000000000000#!/usr/bin/python # -*- coding: utf-8 -*- # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by the # Free Software Foundation; either version 3, 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 MERCHANTIBILITY # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License # for more details. from __future__ import unicode_literals """Argentina National Medical Drug Traceability Program (ANMAT - PAMI - INSSJP)""" __author__ = "Mariano Reingart " __copyright__ = "Copyright (C) 2011 Mariano Reingart" __license__ = "GPL 3.0" import datetime import os import unittest import sys import time if sys.version > '3': basestring = unicode = str from pysimplesoap.client import SoapClient, SoapFault, parse_proxy, \ set_http_wrapper from pysimplesoap.wsse import UsernameToken WSDL = "https://186.153.145.2:9050/trazamed.WebService?wsdl" #https://186.153.145.2:9050/trazamed.WebService?wsdl LOCATION = "https://186.153.145.2:9050/trazamed.WebService" #WSDL = "https://trazabilidad.pami.org.ar:9050/trazamed.WebService?wsdl" class TestTrazamed(unittest.TestCase): internal = 1 def setUp(self): self.client = SoapClient( wsdl=WSDL, cache=None, ns="tzmed", soap_ns="soapenv", soap_server="jetty") # needed to handle list # fix location (localhost:9050 is erroneous in the WSDL) self.client.services['IWebServiceService']['ports']['IWebServicePort']['location'] = LOCATION # Set WSSE security credentials if False: # deprecated, do not use in new code! (just for testing) self.client['wsse:Security'] = { 'wsse:UsernameToken': { 'wsse:Username': "testwservice", 'wsse:Password': "testwservicepsw", } } else: # new recommended style using plugins wsse_token = UsernameToken(username='testwservice', password='testwservicepsw') self.client.plugins.append(wsse_token) def test_send_medicamentos(self): #self.client.help("sendMedicamentos") # Create the complex type (medicament data transfer object): medicamentosDTO = dict( f_evento=datetime.datetime.now().strftime("%d/%m/%Y"), h_evento=datetime.datetime.now().strftime("%H:%M"), gln_origen="9999999999918", gln_destino="glnws", n_remito="1234", n_factura="1234", vencimiento=(datetime.datetime.now() + datetime.timedelta(30)).strftime("%d/%m/%Y"), gtin="GTIN1", lote=datetime.datetime.now().strftime("%Y"), numero_serial=int(time.time()), id_obra_social=None, id_evento=134, cuit_origen="20267565393", cuit_destino="20267565393", apellido="Reingart", nombres="Mariano", tipo_documento="96", n_documento="26756539", sexo="M", direccion="Saraza", numero="1234", piso="", depto="", localidad="Hurlingham", provincia="Buenos Aires", n_postal="1688", fecha_nacimiento="01/01/2000", telefono="5555-5555", ) # Call the webservice to inform a medicament: res = self.client.sendMedicamentos( arg0=medicamentosDTO, arg1='pruebasws', arg2='pruebasws', ) # Analyze the response: ret = res['return'] self.assertIsInstance(ret['codigoTransaccion'], basestring) self.assertEqual(ret['resultado'], True) def test_send_medicamentos_dh_serie(self): self.client.help("sendMedicamentosDHSerie") # Create the complex type (medicament data transfer object): medicamentosDTODHSerie = dict( f_evento=datetime.datetime.now().strftime("%d/%m/%Y"), h_evento=datetime.datetime.now().strftime("%H:%M"), gln_origen="9999999999918", gln_destino="glnws", n_remito="1234", n_factura="1234", vencimiento=(datetime.datetime.now() + datetime.timedelta(30)).strftime("%d/%m/%Y"), gtin="GTIN1", lote=datetime.datetime.now().strftime("%Y"), desde_numero_serial=int(time.time()) + 1, hasta_numero_serial=int(time.time()) - 1, id_obra_social=None, id_evento=134, ) # Call the webservice to inform a medicament: res = self.client.sendMedicamentosDHSerie( arg0=medicamentosDTODHSerie, arg1='pruebasws', arg2='pruebasws', ) # Analyze the response: ret = res['return'] # Check the results: self.assertIsInstance(ret['codigoTransaccion'], basestring) self.assertEqual(ret['errores'][0]['_c_error'], '3004') self.assertEqual(ret['errores'][0]['_d_error'], "El campo Hasta Nro Serial debe ser mayor o igual al campo Desde Nro Serial.") self.assertEqual(ret['resultado'], False) def test_get_transacciones_no_confirmadas(self): # Call the webservice to query all the un-confirmed transactions: res = self.client.getTransaccionesNoConfirmadas( arg0='pruebasws', arg1='pruebasws', arg10='01/01/2013', arg11='31/12/2013', ) # Analyze the response: ret = res['return'] # Check the results (a list should be returned): self.assertIsInstance(ret['list'], list) for transaccion_plain_ws in ret['list']: # each item of the list is a dict (transaccionPlainWS complex type): # {'_f_evento': u'20/06/2012', '_numero_serial': u'04', ...} # check the keys returned in the complex type: for key in ['_f_evento', '_f_transaccion', '_lote', '_numero_serial', '_razon_social_destino', '_gln_destino', '_n_remito', '_vencimiento', '_d_evento', '_id_transaccion_global', '_razon_social_origen', '_n_factura', '_gln_origen', '_id_transaccion', '_gtin', '_nombre']: self.assertIn(key, transaccion_plain_ws) if __name__ == '__main__': unittest.main() pysimplesoap-1.16/tests/wsfexv1_getcmp.xml000066400000000000000000000037221245630617100210250ustar00rootroot00000000000000 300000000022 20111122 19 3 38 1 N 212 EXTERIOR - EEUU 51600002124 x - (0000) - a - EEUU PES 1 130.21 CONTADO C/ENTREGA MERCADERIA CIF 0 1 002 PINZA AMPEROMETRICA 1 7 130.21 0 130.21 20111202 61473001385110 A 0 OK 0