Blame bugzilla/transport.py

Packit 436967
# This program is free software; you can redistribute it and/or modify it
Packit 436967
# under the terms of the GNU General Public License as published by the
Packit 436967
# Free Software Foundation; either version 2 of the License, or (at your
Packit 436967
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
Packit 436967
# the full text of the license.
Packit 436967
Packit 436967
from logging import getLogger
Packit 436967
import sys
Packit 436967
Packit 436967
# pylint: disable=import-error
Packit 436967
if sys.version_info[0] >= 3:
Packit 436967
    from configparser import ConfigParser
Packit 436967
    from urllib.parse import urlparse  # pylint: disable=no-name-in-module
Packit 436967
    from xmlrpc.client import Fault, ProtocolError, ServerProxy, Transport
Packit 436967
else:
Packit 436967
    from ConfigParser import SafeConfigParser as ConfigParser
Packit 436967
    from urlparse import urlparse
Packit 436967
    from xmlrpclib import Fault, ProtocolError, ServerProxy, Transport
Packit 436967
# pylint: enable=import-error
Packit 436967
Packit 436967
import requests
Packit 436967
Packit 436967
Packit 436967
log = getLogger(__name__)
Packit 436967
Packit 436967
Packit 436967
class BugzillaError(Exception):
Packit 436967
    """
Packit 436967
    Error raised in the Bugzilla client code.
Packit 436967
    """
Packit 436967
    pass
Packit 436967
Packit 436967
Packit 436967
class _BugzillaTokenCache(object):
Packit 436967
    """
Packit 436967
    Cache for tokens, including, with apologies for the duplicative
Packit 436967
    terminology, both Bugzilla Tokens and API Keys.
Packit 436967
    """
Packit 436967
Packit 436967
    def __init__(self, uri, tokenfilename):
Packit 436967
        self.tokenfilename = tokenfilename
Packit 436967
        self.tokenfile = ConfigParser()
Packit 436967
        self.domain = urlparse(uri)[1]
Packit 436967
Packit 436967
        if self.tokenfilename:
Packit 436967
            self.tokenfile.read(self.tokenfilename)
Packit 436967
Packit 436967
        if self.domain not in self.tokenfile.sections():
Packit 436967
            self.tokenfile.add_section(self.domain)
Packit 436967
Packit 436967
    @property
Packit 436967
    def value(self):
Packit 436967
        if self.tokenfile.has_option(self.domain, 'token'):
Packit 436967
            return self.tokenfile.get(self.domain, 'token')
Packit 436967
        else:
Packit 436967
            return None
Packit 436967
Packit 436967
    @value.setter
Packit 436967
    def value(self, value):
Packit 436967
        if self.value == value:
Packit 436967
            return
Packit 436967
Packit 436967
        if value is None:
Packit 436967
            self.tokenfile.remove_option(self.domain, 'token')
Packit 436967
        else:
Packit 436967
            self.tokenfile.set(self.domain, 'token', value)
Packit 436967
Packit 436967
        if self.tokenfilename:
Packit 436967
            with open(self.tokenfilename, 'w') as tokenfile:
Packit 436967
                log.debug("Saving to tokenfile")
Packit 436967
                self.tokenfile.write(tokenfile)
Packit 436967
Packit 436967
    def __repr__(self):
Packit 436967
        return '<Bugzilla Token Cache :: %s>' % self.value
Packit 436967
Packit 436967
Packit 436967
class _BugzillaServerProxy(ServerProxy, object):
Packit 436967
    def __init__(self, uri, tokenfile, *args, **kwargs):
Packit 436967
        super(_BugzillaServerProxy, self).__init__(uri, *args, **kwargs)
Packit 436967
        self.token_cache = _BugzillaTokenCache(uri, tokenfile)
Packit 436967
        self.api_key = None
Packit 436967
Packit 436967
    def use_api_key(self, api_key):
Packit 436967
        self.api_key = api_key
Packit 436967
Packit 436967
    def clear_token(self):
Packit 436967
        self.token_cache.value = None
Packit 436967
Packit 436967
    def _ServerProxy__request(self, methodname, params):
Packit 436967
        if len(params) == 0:
Packit 436967
            params = ({}, )
Packit 436967
Packit 436967
        log.debug("XMLRPC call: %s(%s)", methodname, params[0])
Packit 436967
Packit 436967
        if self.api_key is not None:
Packit 436967
            if 'Bugzilla_api_key' not in params[0]:
Packit 436967
                params[0]['Bugzilla_api_key'] = self.api_key
Packit 436967
        elif self.token_cache.value is not None:
Packit 436967
            if 'Bugzilla_token' not in params[0]:
Packit 436967
                params[0]['Bugzilla_token'] = self.token_cache.value
Packit 436967
Packit 436967
        # pylint: disable=no-member
Packit 436967
        ret = super(_BugzillaServerProxy,
Packit 436967
                self)._ServerProxy__request(methodname, params)
Packit 436967
        # pylint: enable=no-member
Packit 436967
Packit 436967
        if isinstance(ret, dict) and 'token' in ret.keys():
Packit 436967
            self.token_cache.value = ret.get('token')
Packit 436967
        return ret
Packit 436967
Packit 436967
Packit 436967
class _RequestsTransport(Transport):
Packit 436967
    user_agent = 'Python/Bugzilla'
Packit 436967
Packit 436967
    def __init__(self, url, cookiejar=None,
Packit 436967
                 sslverify=True, sslcafile=None, debug=True, cert=None):
Packit 436967
        if hasattr(Transport, "__init__"):
Packit 436967
            Transport.__init__(self, use_datetime=False)
Packit 436967
Packit 436967
        self.verbose = debug
Packit 436967
        self._cookiejar = cookiejar
Packit 436967
Packit 436967
        # transport constructor needs full url too, as xmlrpc does not pass
Packit 436967
        # scheme to request
Packit 436967
        self.scheme = urlparse(url)[0]
Packit 436967
        if self.scheme not in ["http", "https"]:
Packit 436967
            raise Exception("Invalid URL scheme: %s (%s)" % (self.scheme, url))
Packit 436967
Packit 436967
        self.use_https = self.scheme == 'https'
Packit 436967
Packit 436967
        self.request_defaults = {
Packit 436967
            'cert': sslcafile if self.use_https else None,
Packit 436967
            'cookies': cookiejar,
Packit 436967
            'verify': sslverify,
Packit 436967
            'headers': {
Packit 436967
                'Content-Type': 'text/xml',
Packit 436967
                'User-Agent': self.user_agent,
Packit 436967
            }
Packit 436967
        }
Packit 436967
Packit 436967
        # Using an explicit Session, rather than requests.get, will use
Packit 436967
        # HTTP KeepAlive if the server supports it.
Packit 436967
        self.session = requests.Session()
Packit 436967
        if cert:
Packit 436967
            self.session.cert = cert
Packit 436967
Packit 436967
    def parse_response(self, response):
Packit 436967
        """
Packit 436967
        Parse XMLRPC response
Packit 436967
        """
Packit 436967
        parser, unmarshaller = self.getparser()
Packit 436967
        parser.feed(response.text.encode('utf-8'))
Packit 436967
        parser.close()
Packit 436967
        return unmarshaller.close()
Packit 436967
Packit 436967
    def _request_helper(self, url, request_body):
Packit 436967
        """
Packit 436967
        A helper method to assist in making a request and provide a parsed
Packit 436967
        response.
Packit 436967
        """
Packit 436967
        response = None
Packit 436967
        try:
Packit 436967
            response = self.session.post(
Packit 436967
                url, data=request_body, **self.request_defaults)
Packit 436967
Packit 436967
            # We expect utf-8 from the server
Packit 436967
            response.encoding = 'UTF-8'
Packit 436967
Packit 436967
            # update/set any cookies
Packit 436967
            if self._cookiejar is not None:
Packit 436967
                for cookie in response.cookies:
Packit 436967
                    self._cookiejar.set_cookie(cookie)
Packit 436967
Packit 436967
                if self._cookiejar.filename is not None:
Packit 436967
                    # Save is required only if we have a filename
Packit 436967
                    self._cookiejar.save()
Packit 436967
Packit 436967
            response.raise_for_status()
Packit 436967
            return self.parse_response(response)
Packit 436967
        except requests.RequestException as e:
Packit 436967
            if not response:
Packit 436967
                raise
Packit 436967
            raise ProtocolError(
Packit 436967
                url, response.status_code, str(e), response.headers)
Packit 436967
        except Fault:
Packit 436967
            raise
Packit 436967
        except Exception:
Packit 436967
            e = BugzillaError(str(sys.exc_info()[1]))
Packit 436967
            # pylint: disable=attribute-defined-outside-init
Packit 436967
            e.__traceback__ = sys.exc_info()[2]
Packit 436967
            # pylint: enable=attribute-defined-outside-init
Packit 436967
            raise e
Packit 436967
Packit 436967
    def request(self, host, handler, request_body, verbose=0):
Packit 436967
        self.verbose = verbose
Packit 436967
        url = "%s://%s%s" % (self.scheme, host, handler)
Packit 436967
Packit 436967
        # xmlrpclib fails to escape \r
Packit 436967
        request_body = request_body.replace(b'\r', b'
')
Packit 436967
Packit 436967
        return self._request_helper(url, request_body)