module.erp5.SOAPWSDLConnection.py 6.84 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
##############################################################################
#
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
#                    Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# 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 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

import SOAPpy
30
import wstools
31
from Products.AGProjects.patches import SOAPpy_WSDL as WSDL
32
from AccessControl.SecurityInfo import allow_class
33
import threading
34

35 36 37 38 39 40 41
# Exception class.
#  This allows restricted python to handle exceptions without allowing direct
#  import of SOAPpy module (because it should not have to be dependant from
#  underlying interface API, only this file has to be).

class SOAPWSDLException(Exception):

42
  __allow_access_to_unprotected_subobjects__ = 1
43

44
  def __init__(self, code, name, info): # pylint: disable=super-init-not-called
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
    self.code = code
    self.name = name
    self.info = info

  def getCode(self):
    return self.code

  def getName(self):
    return self.name

  def getInfo(self):
    return self.info

  def __str__(self):
    return '<%s at 0x%x: %r %r %r>' % (self.__class__.__name__, id(self),
       self.name, self.code, self.info)

allow_class(SOAPWSDLException)

64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
# Authentication classes.
#  These are SOAP authentication classes.
#  They are supposed to be instanciated and then transmited to WebServiceTool
#  in order to create a connection.

class AuthenticationBase(object):
  """
    Authentication API.

    As SOAP doens't provide a standard authentication method, authentication
    plugins must be written for virtualy each SOAP services.
    This API intends to provide hooks for authentication purposes.

    Overload the methods you need (default methods are NO-OPs).
  """

  def ProxyParameterDictHook(self):
    """
      Return a dictionary of extra parameters to provide to WSDL.Proxy .
    """
    return {}

  def AfterConnectionHook(self, connection):
    """
      This hook is called upon connection. It can be used to exchange
      credentials with remote server.
    """
Jérome Perrin's avatar
Jérome Perrin committed
91

92 93 94 95 96

class NullAuthentication(AuthenticationBase):
  """
    NO-OP authentication.
  """
Jérome Perrin's avatar
Jérome Perrin committed
97

98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118

class HeaderAuthentication(AuthenticationBase):
  """
    Authentication implementation for authentication mechanism based on known
    credentials. Those credentials are put in SOAP packet header, in "auth"
    XML block.
  """
  def __init__(self, auth):
    self._auth = auth

  def ProxyParameterDictHook(self):
    return {'auth': self._auth}

allow_class(HeaderAuthentication)

# Wrappers for SOAPpy objects.
#  These wrappers will be returned by the connector, and can be used in
#  restricted scripts.

class WSDLConnection(object):

119
  __allow_access_to_unprotected_subobjects__ = 1
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141

  def __init__(self, wsdl, credentials, service):
    self._wsdl = wsdl
    self._credentials = credentials
    self._service = service
    self._port_dict = {}

  def __getitem__(self, port_name):
    """
      Connect to requested port.
    """
    try:
      result = self._port_dict[port_name]
    except KeyError:
      kw = self._credentials.ProxyParameterDictHook()
      result = self._port_dict[port_name] = PortWrapper(WSDL.Proxy(
        self._wsdl, service=self._service, port=port_name, **kw))
      self._credentials.AfterConnectionHook(result)
    return result

class PortWrapper(object):

142
  __allow_access_to_unprotected_subobjects__ = 1
143 144 145 146 147

  def __init__(self, port):
    self._port = port

  def __getattr__(self, method_id):
148 149 150 151
    return MethodWrapper(getattr(self._port, method_id))

class MethodWrapper(object):

152
  __allow_access_to_unprotected_subobjects__ = 1
153 154 155 156 157 158 159

  def __init__(self, method):
    self._method = method

  def __call__(self, *args, **kw):
    try:
      return self._method(*args, **kw)
160
    except SOAPpy.Types.faultType as exception:
161
      raise SOAPWSDLException(*exception())
162

163 164 165 166
# SOAPpy says nothing about thread-safeness of parsed WSDL.
# Be on the safe side by using threading.local as a storage for it.
wsdl_cache = threading.local()

167
# XXX: wstools.WSDLTools.WSDL.__del__ calls unlink on an xml document
168 169 170 171 172 173 174 175 176 177
# instance, which happens to fail (AttributeError: NoneType has no attribute
# 'unlink') somewhere down in xml module. As that unlink is only acting on xml
# nodes in memory, it's safe to ignore it.
def WSDL___del__(self):
  if self.document is not None:
    unlink = self.document.unlink
    try:
      unlink()
    except AttributeError:
      pass
178 179

wstools.WSDLTools.WSDL.__del__ = WSDL___del__
180

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
class SOAPWSDLConnection:
  """
    Holds a SOAP connection described by a WSDL file.
    This uses a patch from NGNPro over SOAPpy's WSDL.py file, allowing the
    WSDL to describe multiple ports and adding support for authentication by
    header.

    Note: SOAP doesn't describe a standard way to handle authentication, so it
    might not fit your needs.
  """

  def __init__(self, url, user_name=None, password=None, credentials=None,
               service=None):
    """
      url (string)
        The url of the WSDL file describing an underlying SOAP interface.
      user_name (string or None)
      password (string is None)
        The transport-level (http) credentials to use.
      credentials (AuthenticationBase subclass instance or None)
        The interface-level (SOAP) credentials to use.
      service (string or None)
        The WSDL-described service to connect to.
    """
    self.url = url
    self._user_name = user_name
    self._password = password
    if credentials is None:
      credentials = NullAuthentication()
    self._credentials = credentials
    self._service = service

  def connect(self):
214 215 216
    try:
      wsdl = wsdl_cache.parsed
    except AttributeError:
217
      wsdl = wsdl_cache.parsed = wstools.WSDLTools.WSDLReader().loadFromURL(self.url)
218 219 220 221
    # TODO: transport (http) level authentication using self._user_name and
    # self._password
    return WSDLConnection(wsdl, self._credentials, self._service)