Michal Koutný ccf7f1
# This work is licensed under the GNU GPLv2 or later.
Michal Koutný ccf7f1
# See the COPYING file in the top-level directory.
Michal Koutný ccf7f1
Michal Koutný ccf7f1
from logging import getLogger
Michal Koutný ccf7f1
Michal Koutný ccf7f1
import os
Michal Koutný ccf7f1
import sys
Michal Koutný ccf7f1
import urllib.parse
Michal Koutný ccf7f1
Michal Koutný ccf7f1
import requests
Michal Koutný ccf7f1
Michal Koutný ccf7f1
Michal Koutný ccf7f1
log = getLogger(__name__)
Michal Koutný ccf7f1
Michal Koutný ccf7f1
Michal Koutný ccf7f1
class _BugzillaSession(object):
Michal Koutný ccf7f1
    """
Michal Koutný ccf7f1
    Class to handle the backend agnostic 'requests' setup
Michal Koutný ccf7f1
    """
Michal Koutný ccf7f1
    def __init__(self, url, user_agent,
Michal Koutný ccf7f1
            sslverify, cert, tokencache, api_key,
Michal Koutný ccf7f1
            is_redhat_bugzilla,
Michal Koutný ccf7f1
            requests_session=None):
Michal Koutný ccf7f1
        self._url = url
Michal Koutný ccf7f1
        self._user_agent = user_agent
Michal Koutný ccf7f1
        self._scheme = urllib.parse.urlparse(url)[0]
Michal Koutný ccf7f1
        self._tokencache = tokencache
Michal Koutný ccf7f1
        self._api_key = api_key
Michal Koutný ccf7f1
        self._is_xmlrpc = False
Michal Koutný ccf7f1
        self._use_auth_bearer = False
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        if self._scheme not in ["http", "https"]:
Michal Koutný ccf7f1
            raise Exception("Invalid URL scheme: %s (%s)" % (
Michal Koutný ccf7f1
                self._scheme, url))
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        self._session = requests_session
Michal Koutný ccf7f1
        if not self._session:
Michal Koutný ccf7f1
            self._session = requests.Session()
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        if cert:
Michal Koutný ccf7f1
            self._session.cert = cert
Michal Koutný ccf7f1
        if sslverify is False:
Michal Koutný ccf7f1
            self._session.verify = False
Michal Koutný ccf7f1
        self._session.headers["User-Agent"] = self._user_agent
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        if is_redhat_bugzilla and self._api_key:
Michal Koutný ccf7f1
            self._use_auth_bearer = True
Michal Koutný ccf7f1
            self._session.headers["Authorization"] = (
Michal Koutný ccf7f1
                "Bearer %s" % self._api_key)
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def _get_timeout(self):
Michal Koutný ccf7f1
        # Default to 5 minutes. This is longer than bugzilla.redhat.com's
Michal Koutný ccf7f1
        # apparent 3 minute timeout so shouldn't affect legitimate usage,
Michal Koutný ccf7f1
        # but saves us from indefinite hangs
Michal Koutný ccf7f1
        DEFAULT_TIMEOUT = 300
Michal Koutný ccf7f1
        envtimeout = os.environ.get("PYTHONBUGZILLA_REQUESTS_TIMEOUT")
Michal Koutný ccf7f1
        return float(envtimeout or DEFAULT_TIMEOUT)
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def set_rest_defaults(self):
Michal Koutný ccf7f1
        self._session.headers["Content-Type"] = "application/json"
Michal Koutný ccf7f1
    def set_xmlrpc_defaults(self):
Michal Koutný ccf7f1
        self._is_xmlrpc = True
Michal Koutný ccf7f1
        self._session.headers["Content-Type"] = "text/xml"
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def get_user_agent(self):
Michal Koutný ccf7f1
        return self._user_agent
Michal Koutný ccf7f1
    def get_scheme(self):
Michal Koutný ccf7f1
        return self._scheme
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def get_auth_params(self):
Michal Koutný ccf7f1
        # bugzilla.redhat.com will error if there's auth bits in params
Michal Koutný ccf7f1
        # when Authorization header is used
Michal Koutný ccf7f1
        if self._use_auth_bearer:
Michal Koutný ccf7f1
            return {}
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        # Don't add a token to the params list if an API key is set.
Michal Koutný ccf7f1
        # Keeping API key solo means bugzilla will definitely fail
Michal Koutný ccf7f1
        # if the key expires. Passing in a token could hide that
Michal Koutný ccf7f1
        # fact, which could make it confusing to pinpoint the issue.
Michal Koutný ccf7f1
        if self._api_key:
Michal Koutný ccf7f1
            # Bugzilla 5.0 only supports api_key as a query parameter.
Michal Koutný ccf7f1
            # Bugzilla 5.1+ takes it as a X-BUGZILLA-API-KEY header as well,
Michal Koutný ccf7f1
            # with query param taking preference.
Michal Koutný ccf7f1
            return {"Bugzilla_api_key": self._api_key}
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        token = self._tokencache.get_value(self._url)
Michal Koutný ccf7f1
        if token:
Michal Koutný ccf7f1
            return {"Bugzilla_token": token}
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        return {}
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def get_requests_session(self):
Michal Koutný ccf7f1
        return self._session
Michal Koutný ccf7f1
Michal Koutný ccf7f1
    def request(self, *args, **kwargs):
Michal Koutný ccf7f1
        timeout = self._get_timeout()
Michal Koutný ccf7f1
        if "timeout" not in kwargs:
Michal Koutný ccf7f1
            kwargs["timeout"] = timeout
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        response = self._session.request(*args, **kwargs)
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        if self._is_xmlrpc:
Michal Koutný ccf7f1
            # Yes this still appears to matter for properly decoding unicode
Michal Koutný ccf7f1
            # code points in bugzilla.redhat.com content
Michal Koutný ccf7f1
            response.encoding = "UTF-8"
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        try:
Michal Koutný ccf7f1
            response.raise_for_status()
Michal Koutný ccf7f1
        except Exception as e:
Michal Koutný ccf7f1
            # Scrape the api key out of the returned exception string
Michal Koutný ccf7f1
            message = str(e).replace(self._api_key or "", "")
Michal Koutný ccf7f1
            raise type(e)(message).with_traceback(sys.exc_info()[2])
Michal Koutný ccf7f1
Michal Koutný ccf7f1
        return response