Blame bugzilla/transport.py

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