drop old ssl support + compatrequests
Koji now uses ssl via python-requests - use_old_ssl option is removed - koji.ssl library removed - compatrequests dropped Related: https://pagure.io/koji/issue/467
This commit is contained in:
parent
15bc1155bb
commit
8f22069fda
16 changed files with 26 additions and 756 deletions
|
|
@ -7,7 +7,6 @@ omit =
|
|||
tests/*
|
||||
hub/*
|
||||
util/*
|
||||
koji/ssl/*
|
||||
koji/daemon.py
|
||||
koji/tasks.py
|
||||
|
||||
|
|
|
|||
|
|
@ -5632,7 +5632,6 @@ def get_options():
|
|||
'offline_retry_interval': 120,
|
||||
'keepalive' : True,
|
||||
'timeout' : None,
|
||||
'use_old_ssl' : False,
|
||||
'no_ssl_verify' : False,
|
||||
'use_fast_upload': True,
|
||||
'use_createrepo_c': False,
|
||||
|
|
@ -5661,7 +5660,7 @@ def get_options():
|
|||
elif name in ['offline_retry', 'use_createrepo_c', 'createrepo_skip_stat',
|
||||
'createrepo_update', 'keepalive', 'use_fast_upload',
|
||||
'support_rpm_source_layout', 'krb_rdns', 'krb_canon_host',
|
||||
'build_arch_can_fail', 'use_old_ssl', 'no_ssl_verify']:
|
||||
'build_arch_can_fail', 'no_ssl_verify']:
|
||||
defaults[name] = config.getboolean('kojid', name)
|
||||
elif name in ['plugin', 'plugins']:
|
||||
defaults['plugin'] = value.split()
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@
|
|||
Name: koji
|
||||
Version: 1.15.0
|
||||
Release: %{release}%{?dist}
|
||||
License: LGPLv2 and GPLv2+
|
||||
# koji.ssl libs (from plague) are GPLv2+
|
||||
License: LGPLv2
|
||||
Summary: Build system tools
|
||||
Group: Applications/System
|
||||
URL: https://pagure.io/koji
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ ifeq ($(PYTHON), python3)
|
|||
else
|
||||
PYFILES = $(wildcard *.py)
|
||||
PYSCRIPTS = context.py
|
||||
SUBDIRS = ssl
|
||||
SUBDIRS =
|
||||
endif
|
||||
PYVER := $(shell $(PYTHON) -c 'import sys; print("%.3s" % (sys.version))')
|
||||
PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print(sys.prefix)')
|
||||
|
|
|
|||
|
|
@ -62,10 +62,7 @@ import os.path
|
|||
import pwd
|
||||
import random
|
||||
import re
|
||||
try:
|
||||
import requests
|
||||
except ImportError: #pragma: no cover
|
||||
requests = None
|
||||
import requests
|
||||
try:
|
||||
from requests_kerberos import HTTPKerberosAuth
|
||||
except ImportError: #pragma: no cover
|
||||
|
|
@ -1638,7 +1635,6 @@ def read_config(profile_name, user_config=None):
|
|||
'anon_retry' : None,
|
||||
'offline_retry' : None,
|
||||
'offline_retry_interval' : None,
|
||||
'use_old_ssl' : False,
|
||||
'keepalive' : True,
|
||||
'timeout' : None,
|
||||
'use_fast_upload': False,
|
||||
|
|
@ -1708,7 +1704,7 @@ def read_config(profile_name, user_config=None):
|
|||
#not have a default value set in the option parser.
|
||||
if name in result:
|
||||
if name in ('anon_retry', 'offline_retry', 'keepalive',
|
||||
'use_fast_upload', 'krb_rdns', 'use_old_ssl',
|
||||
'use_fast_upload', 'krb_rdns', 'debug',
|
||||
'debug', 'debug_xmlrpc', 'krb_canon_host'):
|
||||
result[name] = config.getboolean(profile_name, name)
|
||||
elif name in ('max_retries', 'retry_interval',
|
||||
|
|
@ -1916,9 +1912,6 @@ pathinfo = PathInfo()
|
|||
def is_requests_cert_error(e):
|
||||
"""Determine if a requests error is due to a bad cert"""
|
||||
|
||||
if requests is None: #pragma: no cover
|
||||
# We are not using requests, so this is not a requests cert error
|
||||
return False
|
||||
if not isinstance(e, requests.exceptions.SSLError):
|
||||
return False
|
||||
|
||||
|
|
@ -1982,22 +1975,21 @@ def is_conn_error(e):
|
|||
return False
|
||||
if isinstance(e, six.moves.http_client.BadStatusLine):
|
||||
return True
|
||||
if requests is not None:
|
||||
try:
|
||||
if isinstance(e, requests.exceptions.ConnectionError):
|
||||
# we see errors like this in keep alive timeout races
|
||||
# ConnectionError(ProtocolError('Connection aborted.', BadStatusLine("''",)),)
|
||||
e2 = getattr(e, 'args', [None])[0]
|
||||
if isinstance(e2, requests.packages.urllib3.exceptions.ProtocolError):
|
||||
e3 = getattr(e2, 'args', [None, None])[1]
|
||||
if isinstance(e3, six.moves.http_client.BadStatusLine):
|
||||
return True
|
||||
if isinstance(e2, socket.error):
|
||||
# same check as unwrapped socket error
|
||||
if getattr(e, 'errno', None) in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE):
|
||||
return True
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
try:
|
||||
if isinstance(e, requests.exceptions.ConnectionError):
|
||||
# we see errors like this in keep alive timeout races
|
||||
# ConnectionError(ProtocolError('Connection aborted.', BadStatusLine("''",)),)
|
||||
e2 = getattr(e, 'args', [None])[0]
|
||||
if isinstance(e2, requests.packages.urllib3.exceptions.ProtocolError):
|
||||
e3 = getattr(e2, 'args', [None, None])[1]
|
||||
if isinstance(e3, six.moves.http_client.BadStatusLine):
|
||||
return True
|
||||
if isinstance(e2, socket.error):
|
||||
# same check as unwrapped socket error
|
||||
if getattr(e, 'errno', None) in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE):
|
||||
return True
|
||||
except (TypeError, AttributeError):
|
||||
pass
|
||||
# otherwise
|
||||
return False
|
||||
|
||||
|
|
@ -2035,7 +2027,6 @@ def grab_session_options(options):
|
|||
'upload_blocksize',
|
||||
'krb_rdns',
|
||||
'krb_canon_host',
|
||||
'use_old_ssl',
|
||||
'no_ssl_verify',
|
||||
'serverca',
|
||||
)
|
||||
|
|
@ -2070,24 +2061,13 @@ class ClientSession(object):
|
|||
self.rsession = None
|
||||
self.new_session()
|
||||
self.opts.setdefault('timeout', 60 * 60 * 12)
|
||||
self._old_ssl_warning = False
|
||||
|
||||
|
||||
def new_session(self):
|
||||
self.logger.debug("Opening new requests session")
|
||||
if self.rsession:
|
||||
self.rsession.close()
|
||||
if self.opts.get('use_old_ssl', False) or requests is None:
|
||||
if not self._old_ssl_warning:
|
||||
# only warn once per instance
|
||||
self.logger.warn('The use_old_ssl option is deprecated')
|
||||
self._old_ssl_warning = True
|
||||
if not six.PY2:
|
||||
raise GenericError('use_old_ssl is only supported on python2')
|
||||
import koji.compatrequests
|
||||
self.rsession = koji.compatrequests.Session()
|
||||
else:
|
||||
self.rsession = requests.Session()
|
||||
self.rsession = requests.Session()
|
||||
|
||||
def setSession(self, sinfo):
|
||||
"""Set the session info
|
||||
|
|
@ -2230,7 +2210,6 @@ class ClientSession(object):
|
|||
raise PythonImportError(
|
||||
"Please install python-requests-kerberos to use GSSAPI."
|
||||
)
|
||||
|
||||
# force https
|
||||
old_baseurl = self.baseurl
|
||||
uri = six.moves.urllib.parse.urlsplit(self.baseurl)
|
||||
|
|
|
|||
|
|
@ -1,150 +0,0 @@
|
|||
"""
|
||||
koji.compatrequests
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module contains a *very limited* partial implemention of the requests
|
||||
module that is based on the older codepaths in koji. It only provides
|
||||
the bits that koji needs.
|
||||
"""
|
||||
|
||||
import httplib
|
||||
import urlparse
|
||||
import urllib
|
||||
import sys
|
||||
import ssl.SSLCommon
|
||||
from warnings import warn
|
||||
try:
|
||||
from ssl import ssl as pyssl
|
||||
except ImportError: # pragma: no cover
|
||||
pass
|
||||
|
||||
|
||||
class Session(object):
|
||||
|
||||
def __init__(self):
|
||||
self.connection = None
|
||||
warn('koji: compatrequests is deprecated', DeprecationWarning)
|
||||
|
||||
def post(self, url, data=None, headers=None, stream=None, verify=None,
|
||||
cert=None, timeout=None):
|
||||
uri = urlparse.urlsplit(url)
|
||||
if uri[3]:
|
||||
handler = "%s?%s" % (uri[2], uri[3])
|
||||
else:
|
||||
handler = uri[2]
|
||||
cnx = self.get_connection(uri, cert, verify, timeout)
|
||||
#cnx.set_debuglevel(1)
|
||||
cnx.putrequest('POST', handler)
|
||||
if headers:
|
||||
for k in headers:
|
||||
cnx.putheader(k, headers[k])
|
||||
cnx.endheaders()
|
||||
if data is not None:
|
||||
cnx.send(data)
|
||||
response = cnx.getresponse()
|
||||
return Response(self, response)
|
||||
|
||||
def get_connection(self, uri, cert, verify, timeout):
|
||||
scheme = uri[0]
|
||||
host, port = urllib.splitport(uri[1])
|
||||
key = (scheme, host, cert, verify, timeout)
|
||||
#if self.connection and self.opts.get('keepalive'):
|
||||
if self.connection: # XXX honor keepalive
|
||||
if key == self.connection[0]:
|
||||
cnx = self.connection[1]
|
||||
if getattr(cnx, 'sock', None):
|
||||
return cnx
|
||||
# Otherwise we make a new one
|
||||
default_port = 80
|
||||
certs = {}
|
||||
if isinstance(verify, basestring):
|
||||
certs['peer_ca_cert'] = verify
|
||||
if cert:
|
||||
certs['key_and_cert'] = cert
|
||||
ctx = ssl.SSLCommon.CreateSSLContext(certs)
|
||||
cnxOpts = {'ssl_context' : ctx}
|
||||
cnxClass = ssl.SSLCommon.PlgHTTPSConnection
|
||||
default_port = 443
|
||||
elif scheme == 'https':
|
||||
cnxOpts = {}
|
||||
if verify:
|
||||
if sys.version_info[:3] >= (2, 7, 9):
|
||||
try:
|
||||
proto = pyssl.PROTOCOL_TLS
|
||||
except AttributeError:
|
||||
proto = pyssl.PROTOCOL_SSLv23
|
||||
ctx = pyssl.SSLContext(proto)
|
||||
ctx.load_verify_locations(cafile=verify)
|
||||
ctx.verify_mode = pyssl.CERT_REQUIRED
|
||||
cnxOpts['context'] = ctx
|
||||
else:
|
||||
cnxOpts['cert_file'] = verify
|
||||
elif verify is None:
|
||||
# not specified, leave as default
|
||||
pass
|
||||
elif sys.version_info[:3] >= (2, 7, 9):
|
||||
# no verify
|
||||
ctx = pyssl._create_unverified_context()
|
||||
cnxOpts['context'] = ctx
|
||||
cnxClass = httplib.HTTPSConnection
|
||||
default_port = 443
|
||||
elif scheme == 'http':
|
||||
cnxOpts = {}
|
||||
cnxClass = httplib.HTTPConnection
|
||||
else:
|
||||
raise IOError("unsupported protocol: %s" % scheme)
|
||||
|
||||
timeout_compat = False
|
||||
if timeout:
|
||||
if sys.version_info[:3] < (2, 6, 0) and 'ssl_context' not in cnxOpts:
|
||||
timeout_compat = True
|
||||
else:
|
||||
cnxOpts['timeout'] = timeout
|
||||
# no need to close connection
|
||||
port = (port and int(port) or default_port)
|
||||
cnx = cnxClass(host, port, **cnxOpts)
|
||||
self.connection = (key, cnx)
|
||||
if timeout_compat:
|
||||
# in python < 2.6 httplib does not support the timeout option
|
||||
# but socket supports it since 2.3
|
||||
cnx.connect()
|
||||
cnx.sock.settimeout(timeout)
|
||||
return cnx
|
||||
|
||||
def close(self):
|
||||
if self.connection:
|
||||
self.connection[1].close()
|
||||
self.connection = None
|
||||
|
||||
|
||||
class Response(object):
|
||||
|
||||
def __init__(self, session, response):
|
||||
self.session = session
|
||||
self.response = response
|
||||
|
||||
def raise_for_status(self):
|
||||
if self.response.status >= 400:
|
||||
raise httplib.HTTPException("HTTP %s: %s" % (self.response.status,
|
||||
self.response.reason))
|
||||
|
||||
|
||||
def iter_content(self, blocksize=1):
|
||||
# should we check this in Session.post()?
|
||||
# should we even check this here?
|
||||
if self.response.status != 200:
|
||||
if (self.response.getheader("content-length", 0)):
|
||||
self.response.read()
|
||||
# XXX wrong exception
|
||||
raise Exception("Server status: %s" % self.response.status)
|
||||
while True:
|
||||
chunk = self.response.read(blocksize)
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
|
||||
def close(self):
|
||||
self.response.close()
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
FILES = $(wildcard *.py)
|
||||
|
||||
PYTHON=python
|
||||
|
||||
_default:
|
||||
@echo "nothing to make. try make install"
|
||||
|
||||
clean:
|
||||
rm -f *.o *.so *.pyc *~
|
||||
|
||||
install:
|
||||
@if [ "$(DESTDIR)" = "" ]; then \
|
||||
echo " "; \
|
||||
echo "ERROR: A destdir is required"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
mkdir -p $(DESTDIR)
|
||||
install -p -m 644 $(FILES) $(DESTDIR)
|
||||
|
||||
$(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)', 1, '$(PYDIR)', 1)"
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
# 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 Library 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.
|
||||
#
|
||||
# Copyright 2005 Dan Williams <dcbw@redhat.com> and Red Hat, Inc.
|
||||
|
||||
import os, sys
|
||||
from OpenSSL import SSL
|
||||
import SSLConnection
|
||||
import httplib
|
||||
import socket
|
||||
|
||||
def our_verify(connection, x509, errNum, errDepth, preverifyOK):
|
||||
# print("Verify: errNum = %s, errDepth = %s, preverifyOK = %s" % (errNum, errDepth, preverifyOK))
|
||||
|
||||
# preverifyOK should tell us whether or not the client's certificate
|
||||
# correctly authenticates against the CA chain
|
||||
return preverifyOK
|
||||
|
||||
|
||||
def CreateSSLContext(certs):
|
||||
key_and_cert = certs['key_and_cert']
|
||||
peer_ca_cert = certs['peer_ca_cert']
|
||||
for f in key_and_cert, peer_ca_cert:
|
||||
if f and not os.access(f, os.R_OK):
|
||||
raise Exception("%s does not exist or is not readable" % f)
|
||||
|
||||
ctx = SSL.Context(SSL.SSLv23_METHOD) # Use best possible TLS Method
|
||||
ctx.use_certificate_file(key_and_cert)
|
||||
ctx.use_privatekey_file(key_and_cert)
|
||||
ctx.load_verify_locations(peer_ca_cert)
|
||||
verify = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT
|
||||
ctx.set_verify(verify, our_verify)
|
||||
ctx.set_verify_depth(10)
|
||||
ctx.set_options(SSL.OP_NO_SSLv3 | SSL.OP_NO_SSLv2) # disable SSLv2 and SSLv3
|
||||
return ctx
|
||||
|
||||
|
||||
class PlgHTTPSConnection(httplib.HTTPConnection):
|
||||
"This class allows communication via SSL."
|
||||
|
||||
response_class = httplib.HTTPResponse
|
||||
|
||||
def __init__(self, host, port=None, ssl_context=None, strict=None, timeout=None):
|
||||
httplib.HTTPConnection.__init__(self, host, port, strict)
|
||||
self.ssl_ctx = ssl_context
|
||||
self._timeout = timeout
|
||||
|
||||
def connect(self):
|
||||
for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
try:
|
||||
sock = socket.socket(af, socktype, proto)
|
||||
con = SSL.Connection(self.ssl_ctx, sock)
|
||||
self.sock = SSLConnection.SSLConnection(con)
|
||||
if sys.version_info[:3] >= (2, 3, 0):
|
||||
self.sock.settimeout(self._timeout)
|
||||
self.sock.connect(sa)
|
||||
if self.debuglevel > 0:
|
||||
print("connect: (%s, %s) [ssl]" % (self.host, self.port))
|
||||
except socket.error:
|
||||
if self.debuglevel > 0:
|
||||
print('connect fail:', (self.host, self.port))
|
||||
if self.sock:
|
||||
self.sock.close()
|
||||
self.sock = None
|
||||
continue
|
||||
break
|
||||
else:
|
||||
raise socket.error("failed to connect")
|
||||
|
|
@ -1,162 +0,0 @@
|
|||
# Higher-level SSL objects used by rpclib
|
||||
#
|
||||
# Copyright (c) 2002 Red Hat, Inc.
|
||||
#
|
||||
# Author: Mihai Ibanescu <misa@redhat.com>
|
||||
# Modifications by Dan Williams <dcbw@redhat.com>
|
||||
|
||||
|
||||
from OpenSSL import SSL
|
||||
import time, socket, select
|
||||
|
||||
|
||||
class SSLConnection:
|
||||
"""
|
||||
This whole class exists just to filter out a parameter
|
||||
passed in to the shutdown() method in SimpleXMLRPC.doPOST()
|
||||
"""
|
||||
|
||||
DEFAULT_TIMEOUT = 600
|
||||
|
||||
def __init__(self, conn):
|
||||
"""
|
||||
Connection is not yet a new-style class,
|
||||
so I'm making a proxy instead of subclassing.
|
||||
"""
|
||||
self.__dict__["conn"] = conn
|
||||
self.__dict__["close_refcount"] = 1
|
||||
self.__dict__["closed"] = False
|
||||
self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
|
||||
|
||||
def __del__(self):
|
||||
self.__dict__["conn"].close()
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.__dict__["conn"], name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
setattr(self.__dict__["conn"], name, value)
|
||||
|
||||
def settimeout(self, timeout):
|
||||
if timeout == None:
|
||||
self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
|
||||
else:
|
||||
self.__dict__["timeout"] = timeout
|
||||
self.__dict__["conn"].settimeout(timeout)
|
||||
|
||||
def shutdown(self, how=1):
|
||||
"""
|
||||
SimpleXMLRpcServer.doPOST calls shutdown(1),
|
||||
and Connection.shutdown() doesn't take
|
||||
an argument. So we just discard the argument.
|
||||
"""
|
||||
self.__dict__["conn"].shutdown()
|
||||
|
||||
def accept(self):
|
||||
"""
|
||||
This is the other part of the shutdown() workaround.
|
||||
Since servers create new sockets, we have to infect
|
||||
them with our magic. :)
|
||||
"""
|
||||
c, a = self.__dict__["conn"].accept()
|
||||
return (SSLConnection(c), a)
|
||||
|
||||
def makefile(self, mode='r', bufsize=-1):
|
||||
"""
|
||||
We need to use socket._fileobject Because SSL.Connection
|
||||
doesn't have a 'dup'. Not exactly sure WHY this is, but
|
||||
this is backed up by comments in socket.py and SSL/connection.c
|
||||
|
||||
Since httplib.HTTPSResponse/HTTPConnection depend on the
|
||||
socket being duplicated when they close it, we refcount the
|
||||
socket object and don't actually close until its count is 0.
|
||||
"""
|
||||
self.__dict__["close_refcount"] = self.__dict__["close_refcount"] + 1
|
||||
return PlgFileObject(self, mode, bufsize)
|
||||
|
||||
def close(self):
|
||||
if self.__dict__["closed"]:
|
||||
return
|
||||
self.__dict__["close_refcount"] = self.__dict__["close_refcount"] - 1
|
||||
if self.__dict__["close_refcount"] == 0:
|
||||
self.shutdown()
|
||||
self.__dict__["conn"].close()
|
||||
self.__dict__["closed"] = True
|
||||
|
||||
def sendall(self, data, flags=0):
|
||||
"""
|
||||
- Use select() to simulate a socket timeout without setting the socket
|
||||
to non-blocking mode.
|
||||
- Don't use pyOpenSSL's sendall() either, since it just loops on WantRead
|
||||
or WantWrite, consuming 100% CPU, and never times out.
|
||||
"""
|
||||
timeout = self.__dict__["timeout"]
|
||||
con = self.__dict__["conn"]
|
||||
(read, write, excpt) = select.select([], [con], [], timeout)
|
||||
if not con in write:
|
||||
raise socket.timeout((110, "Operation timed out."))
|
||||
|
||||
starttime = time.time()
|
||||
origlen = len(data)
|
||||
sent = -1
|
||||
while len(data):
|
||||
curtime = time.time()
|
||||
if curtime - starttime > timeout:
|
||||
raise socket.timeout((110, "Operation timed out."))
|
||||
|
||||
try:
|
||||
sent = con.send(data, flags)
|
||||
except SSL.SysCallError as e:
|
||||
if e[0] == 32: # Broken Pipe
|
||||
self.close()
|
||||
sent = 0
|
||||
else:
|
||||
raise socket.error(e)
|
||||
except (SSL.WantWriteError, SSL.WantReadError):
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
|
||||
data = data[sent:]
|
||||
return origlen - len(data)
|
||||
|
||||
def recv(self, bufsize, flags=0):
|
||||
"""
|
||||
Use select() to simulate a socket timeout without setting the socket
|
||||
to non-blocking mode
|
||||
"""
|
||||
timeout = self.__dict__["timeout"]
|
||||
con = self.__dict__["conn"]
|
||||
(read, write, excpt) = select.select([con], [], [], timeout)
|
||||
if not con in read:
|
||||
raise socket.timeout((110, "Operation timed out."))
|
||||
|
||||
starttime = time.time()
|
||||
while True:
|
||||
curtime = time.time()
|
||||
if curtime - starttime > timeout:
|
||||
raise socket.timeout((110, "Operation timed out."))
|
||||
|
||||
try:
|
||||
return con.recv(bufsize, flags)
|
||||
except SSL.ZeroReturnError:
|
||||
return None
|
||||
except SSL.WantReadError:
|
||||
time.sleep(0.2)
|
||||
except SSL.SysCallError as e:
|
||||
if e.args == (-1, 'Unexpected EOF'):
|
||||
break
|
||||
raise
|
||||
return None
|
||||
|
||||
class PlgFileObject(socket._fileobject):
|
||||
def close(self):
|
||||
"""
|
||||
socket._fileobject doesn't actually _close_ the socket,
|
||||
which we want it to do, so we have to override.
|
||||
"""
|
||||
try:
|
||||
if self._sock:
|
||||
self.flush()
|
||||
self._sock.close()
|
||||
finally:
|
||||
self._sock = None
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
# identify this as the ssl module
|
||||
|
||||
# our own ssl submodule masks python's in the main lib, so we import this here
|
||||
try:
|
||||
import ssl # python's ssl module
|
||||
except ImportError: # pragma: no cover
|
||||
# ssl module added in 2.6
|
||||
pass
|
||||
|
|
@ -34,30 +34,17 @@ class TestClientSession(unittest.TestCase):
|
|||
|
||||
@mock.patch('requests.Session')
|
||||
def test_new_session(self, rsession):
|
||||
opts = {'use_old_ssl': False}
|
||||
ksession = koji.ClientSession('http://koji.example.com/kojihub', opts)
|
||||
koji.ClientSession('http://koji.example.com/kojihub')
|
||||
|
||||
# init should have called new_session for us
|
||||
|
||||
rsession.assert_called_once()
|
||||
|
||||
@mock.patch('requests.Session')
|
||||
def test_new_session_old(self, rsession):
|
||||
if six.PY3:
|
||||
return
|
||||
opts = {'use_old_ssl': True}
|
||||
ksession = koji.ClientSession('http://koji.example.com/kojihub', opts)
|
||||
|
||||
# init should have called new_session for us
|
||||
|
||||
rsession.assert_not_called()
|
||||
|
||||
@mock.patch('requests.Session')
|
||||
def test_new_session_close(self, rsession):
|
||||
if six.PY3:
|
||||
return
|
||||
opts = {'use_old_ssl': True}
|
||||
ksession = koji.ClientSession('http://koji.example.com/kojihub', opts)
|
||||
ksession = koji.ClientSession('http://koji.example.com/kojihub')
|
||||
my_rsession = mock.MagicMock()
|
||||
ksession.rsession = my_rsession
|
||||
|
||||
|
|
@ -69,7 +56,7 @@ class TestClientSession(unittest.TestCase):
|
|||
class TestFastUpload(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.ksession = koji.ClientSession('http://koji.example.com/kojihub', {})
|
||||
self.ksession = koji.ClientSession('http://koji.example.com/kojihub')
|
||||
self.do_fake_login()
|
||||
# mocks
|
||||
self.ksession._callMethod = mock.MagicMock()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ NORMAL_VAL = {'user': 'jdoe',
|
|||
'upload_blocksize': 1024,
|
||||
'krb_rdns': 'fakerdns',
|
||||
'krb_canon_host': 'fakehost',
|
||||
'use_old_ssl': False,
|
||||
'no_ssl_verify': True,
|
||||
'serverca': '/fake/serverca.cert',
|
||||
}
|
||||
|
|
@ -43,7 +42,6 @@ NONE_VAL = {'user': None,
|
|||
'upload_blocksize': None,
|
||||
'krb_rdns': None,
|
||||
'krb_canon_host': None,
|
||||
'use_old_ssl': None,
|
||||
'no_ssl_verify': None,
|
||||
'serverca': None,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,267 +0,0 @@
|
|||
import httplib
|
||||
import mock
|
||||
import unittest
|
||||
import urlparse
|
||||
|
||||
import koji.compatrequests
|
||||
|
||||
|
||||
class TestResponse(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
session = mock.MagicMock()
|
||||
response = mock.MagicMock()
|
||||
self.response = koji.compatrequests.Response(session, response)
|
||||
|
||||
def tearDown(self):
|
||||
del self.response
|
||||
|
||||
def test_read(self):
|
||||
self.response.response.status = 200
|
||||
data = [
|
||||
"Here's some data",
|
||||
"Here's some mooore data",
|
||||
"And look!",
|
||||
"Here's a nice block of lorem text",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
|
||||
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut "
|
||||
"enim ad minim veniam, quis nostrud exercitation ullamco laboris "
|
||||
"nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor "
|
||||
"in reprehenderit in voluptate velit esse cillum dolore eu fugiat "
|
||||
"nulla pariatur. Excepteur sint occaecat cupidatat non proident, "
|
||||
"sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||
"", #eof
|
||||
]
|
||||
self.response.response.read.side_effect = data
|
||||
|
||||
result = list(self.response.iter_content(blocksize=10240))
|
||||
|
||||
self.assertEqual(result, data[:-1])
|
||||
rcalls = [mock.call(10240) for s in data]
|
||||
self.response.response.read.assert_has_calls(rcalls)
|
||||
|
||||
self.response.close()
|
||||
self.response.response.close.assert_called_once()
|
||||
|
||||
def test_error(self):
|
||||
self.response.response.status = 404
|
||||
self.response.response.getheader.return_value = 0
|
||||
with self.assertRaises(Exception):
|
||||
list(self.response.iter_content(8192))
|
||||
self.response.response.read.assert_not_called()
|
||||
|
||||
self.response.response.status = 404
|
||||
self.response.response.getheader.return_value = 42
|
||||
with self.assertRaises(Exception):
|
||||
list(self.response.iter_content(8192))
|
||||
self.response.response.read.assert_called_once()
|
||||
|
||||
self.response.response.status = 404
|
||||
self.response.response.reason = 'Not Found'
|
||||
self.response.response.getheader.return_value = 42
|
||||
with self.assertRaises(httplib.HTTPException):
|
||||
self.response.raise_for_status()
|
||||
|
||||
|
||||
|
||||
class TestSessionPost(unittest.TestCase):
|
||||
|
||||
def test_simple(self):
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'https://www.fakedomain.org/KOJIHUB'
|
||||
cnx = mock.MagicMock()
|
||||
session.get_connection = mock.MagicMock()
|
||||
session.get_connection.return_value = cnx
|
||||
response = mock.MagicMock()
|
||||
cnx.getresponse.return_value = response
|
||||
|
||||
ret = session.post(url, data="data", headers={"foo": "bar"})
|
||||
cnx.putrequest.assert_called_once_with('POST', '/KOJIHUB')
|
||||
cnx.putheader.assert_called_once_with('foo', 'bar')
|
||||
cnx.send.assert_called_once_with("data")
|
||||
self.assertEqual(ret.response, response)
|
||||
|
||||
def test_less_simple(self):
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'https://www.fakedomain.org/KOJIHUB?a=1&b=2'
|
||||
cnx = mock.MagicMock()
|
||||
session.get_connection = mock.MagicMock()
|
||||
session.get_connection.return_value = cnx
|
||||
response = mock.MagicMock()
|
||||
cnx.getresponse.return_value = response
|
||||
|
||||
ret = session.post(url, data="data", headers={"foo": "bar"},
|
||||
cert="cert", verify="verify", stream=True, timeout=1701)
|
||||
cnx.putrequest.assert_called_once_with('POST', '/KOJIHUB?a=1&b=2')
|
||||
cnx.putheader.assert_called_once_with('foo', 'bar')
|
||||
cnx.send.assert_called_once_with("data")
|
||||
self.assertEqual(ret.response, response)
|
||||
|
||||
|
||||
class TestSessionConnection(unittest.TestCase):
|
||||
|
||||
@mock.patch('httplib.HTTPConnection')
|
||||
def test_http(self, HTTPConnection):
|
||||
# no cert, no verify, no timeout
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'http://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
|
||||
cnx = session.get_connection(uri, None, None, None)
|
||||
HTTPConnection.assert_called_once_with('www.fakedomain234234.org', 80)
|
||||
key = ('http', 'www.fakedomain234234.org', None, None, None)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
|
||||
# and close it
|
||||
session.close()
|
||||
self.assertEqual(session.connection, None)
|
||||
cnx.close.assert_called_with()
|
||||
|
||||
# double close should not error
|
||||
session.close()
|
||||
|
||||
def test_cached(self):
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'http://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
key = ('http', 'www.fakedomain234234.org', None, None, None)
|
||||
cnx = mock.MagicMock()
|
||||
session.connection = (key, cnx)
|
||||
|
||||
ret = session.get_connection(uri, None, None, None)
|
||||
self.assertEqual(ret, cnx)
|
||||
|
||||
def test_badproto(self):
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'nosuchproto://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
|
||||
with self.assertRaises(IOError):
|
||||
ret = session.get_connection(uri, None, None, None)
|
||||
|
||||
@mock.patch('httplib.HTTPConnection')
|
||||
@mock.patch('sys.version_info', new=(2, 7, 12, 'final', 0))
|
||||
def test_timeout(self, HTTPConnection):
|
||||
# no cert, no verify
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'http://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
timeout = 1701
|
||||
|
||||
cnx = session.get_connection(uri, None, None, 1701)
|
||||
HTTPConnection.assert_called_once_with('www.fakedomain234234.org', 80, timeout=1701)
|
||||
key = ('http', 'www.fakedomain234234.org', None, None, 1701)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
|
||||
@mock.patch('httplib.HTTPConnection')
|
||||
@mock.patch('sys.version_info', new=(2, 4, 3, 'final', 0))
|
||||
def test_timeout_compat(self, HTTPConnection):
|
||||
# no cert, no verify
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'http://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
timeout = 1701
|
||||
|
||||
cnx = session.get_connection(uri, None, None, 1701)
|
||||
HTTPConnection.assert_called_once_with('www.fakedomain234234.org', 80)
|
||||
key = ('http', 'www.fakedomain234234.org', None, None, 1701)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
cnx.connect.assert_called_once()
|
||||
cnx.sock.settimeout.assert_called_with(1701)
|
||||
|
||||
@mock.patch('httplib.HTTPSConnection')
|
||||
def test_https(self, HTTPSConnection):
|
||||
# no cert, no verify, no timeout
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
|
||||
cnx = session.get_connection(uri, None, None, None)
|
||||
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443)
|
||||
key = ('https', 'www.fakedomain234234.org', None, None, None)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
|
||||
@mock.patch('koji.ssl.SSLCommon.CreateSSLContext')
|
||||
@mock.patch('koji.ssl.SSLCommon.PlgHTTPSConnection')
|
||||
def test_cert(self, PlgHTTPSConnection, CreateSSLContext):
|
||||
# no verify, no timeout
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
cert = '/path/to/cert/file'
|
||||
context = mock.MagicMock()
|
||||
CreateSSLContext.return_value = context
|
||||
|
||||
cnx = session.get_connection(uri, cert, None, None)
|
||||
PlgHTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443, ssl_context=context)
|
||||
key = ('https', 'www.fakedomain234234.org', cert, None, None)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
|
||||
@mock.patch('ssl._create_unverified_context')
|
||||
@mock.patch('httplib.HTTPSConnection')
|
||||
@mock.patch('sys.version_info', new=(2, 7, 12, 'final', 0))
|
||||
def test_unverified(self, HTTPSConnection, create_unverified_context):
|
||||
# no cert, verify=False, no timeout
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
context = mock.MagicMock()
|
||||
create_unverified_context.return_value = context
|
||||
|
||||
cnx = session.get_connection(uri, None, False, None)
|
||||
create_unverified_context.assert_called_once()
|
||||
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443, context=context)
|
||||
key = ('https', 'www.fakedomain234234.org', None, False, None)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
|
||||
@mock.patch('httplib.HTTPSConnection')
|
||||
@mock.patch('sys.version_info', new=(2, 4, 3, 'final', 0))
|
||||
def test_unverified_compat(self, HTTPSConnection):
|
||||
# no cert, verify=False, no timeout
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
|
||||
cnx = session.get_connection(uri, None, False, None)
|
||||
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443)
|
||||
key = ('https', 'www.fakedomain234234.org', None, False, None)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
|
||||
@mock.patch('ssl._create_unverified_context')
|
||||
@mock.patch('ssl.SSLContext')
|
||||
@mock.patch('httplib.HTTPSConnection')
|
||||
@mock.patch('sys.version_info', new=(2, 7, 12, 'final', 0))
|
||||
def test_verify(self, HTTPSConnection, SSLContext, create_unverified_context):
|
||||
# no cert, no timeout
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
context = mock.MagicMock()
|
||||
SSLContext.return_value = context
|
||||
verify = '/path/to/verify/cert'
|
||||
|
||||
cnx = session.get_connection(uri, None, verify, None)
|
||||
create_unverified_context.assert_not_called()
|
||||
SSLContext.assert_called_once()
|
||||
context.load_verify_locations.called_once_with(cafile=verify)
|
||||
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443, context=context)
|
||||
key = ('https', 'www.fakedomain234234.org', None, verify, None)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
|
||||
@mock.patch('ssl._create_unverified_context')
|
||||
@mock.patch('ssl.SSLContext')
|
||||
@mock.patch('httplib.HTTPSConnection')
|
||||
@mock.patch('sys.version_info', new=(2, 4, 3, 'final', 0))
|
||||
def test_verify_compat(self, HTTPSConnection, SSLContext, create_unverified_context):
|
||||
# no cert, no timeout
|
||||
session = koji.compatrequests.Session()
|
||||
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
|
||||
uri = urlparse.urlsplit(url)
|
||||
verify = '/path/to/verify/cert'
|
||||
|
||||
cnx = session.get_connection(uri, None, verify, None)
|
||||
create_unverified_context.assert_not_called()
|
||||
SSLContext.assert_not_called()
|
||||
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443, cert_file=verify)
|
||||
key = ('https', 'www.fakedomain234234.org', None, verify, None)
|
||||
self.assertEqual(session.connection, (key, cnx))
|
||||
|
|
@ -154,7 +154,6 @@ def get_options():
|
|||
['unprotected_keys', None, 'string'],
|
||||
['grace_period', None, 'string'],
|
||||
['trashcan_tag', None, 'string'],
|
||||
['use_old_ssl', None, 'boolean'],
|
||||
['no_ssl_verify', None, 'boolean'],
|
||||
['timeout', None, 'integer'],
|
||||
]
|
||||
|
|
|
|||
|
|
@ -744,7 +744,6 @@ def get_options():
|
|||
'max_retries': 120,
|
||||
'offline_retry': True,
|
||||
'offline_retry_interval': 120,
|
||||
'use_old_ssl': False,
|
||||
'no_ssl_verify': False,
|
||||
'max_delete_processes': 4,
|
||||
'max_repo_tasks' : 4,
|
||||
|
|
@ -767,7 +766,7 @@ def get_options():
|
|||
str_opts = ('topdir', 'server', 'user', 'password', 'logfile', 'principal', 'keytab', 'krbservice',
|
||||
'cert', 'ca', 'serverca', 'debuginfo_tags', 'source_tags') # FIXME: remove ca here
|
||||
bool_opts = ('with_src','verbose','debug','ignore_stray_repos', 'offline_retry',
|
||||
'krb_rdns', 'krb_canon_host', 'use_old_ssl', 'no_ssl_verify')
|
||||
'krb_rdns', 'krb_canon_host', 'no_ssl_verify')
|
||||
for name in config.options(section):
|
||||
if name in int_opts:
|
||||
defaults[name] = config.getint(section, name)
|
||||
|
|
|
|||
|
|
@ -127,7 +127,6 @@ def get_options():
|
|||
'server': None,
|
||||
'user': None,
|
||||
'password': None,
|
||||
'use_old_ssl': False,
|
||||
'no_ssl_verify': False,
|
||||
'retry_interval': 60,
|
||||
'max_retries': 120,
|
||||
|
|
@ -147,7 +146,7 @@ def get_options():
|
|||
except ValueError:
|
||||
quit("value for %s option must be a valid integer" % name)
|
||||
elif name in ['offline_retry', 'krb_rdns', 'krb_canon_host',
|
||||
'use_old_ssl', 'no_ssl_verify']:
|
||||
'no_ssl_verify']:
|
||||
defaults[name] = config.getboolean('kojivmd', name)
|
||||
elif name in ['plugin', 'plugins']:
|
||||
defaults['plugin'] = value.split()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue