|
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)
|