Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 1.24.2 #1564

Merged
merged 4 commits into from Apr 17, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGES.rst
@@ -1,12 +1,23 @@
Changes
=======

1.24.2 (2019-04-17)
-------------------

* Don't load system certificates by default when any other ``ca_certs``, ``ca_certs_dir`` or
``ssl_context`` parameters are specified.

* Remove Authorization header regardless of case when redirecting to cross-site. (Issue #1510)

* Add support for IPv6 addresses in subjectAltName section of certificates. (Issue #1269)


1.24.1 (2018-11-02)
-------------------

* Remove quadratic behavior within ``GzipDecoder.decompress()`` (Issue #1467)

* Restored functionality of `ciphers` parameter for `create_urllib3_context()`. (Issue #1462)
* Restored functionality of ``ciphers`` parameter for ``create_urllib3_context()``. (Issue #1462)


1.24 (2018-10-16)
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTORS.txt
Expand Up @@ -272,5 +272,8 @@ In chronological order:
* Justin Bramley <https://github.com/jbramleycl>
* Add ability to handle multiple Content-Encodings

* Katsuhiko YOSHIDA <https://github.com/kyoshidajp>
* Remove Authorization header regardless of case when redirecting to cross-site

* [Your name or handle] <[email or website]>
* [Brief summary of your changes]
16 changes: 16 additions & 0 deletions dummyserver/certs/server.ipv6_san.crt
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICfTCCAeagAwIBAgIJAPcpn3/M5+piMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
aWRnaXRzIFB0eSBMdGQwHhcNMTgxMjE5MDUyMjUyWhcNNDgxMjE4MDUyMjUyWjBF
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQDXe3FqmCWvP8XPxqtT+0bfL1Tvzvebi46k0WIcUV8bP3vyYiSRXG9ALmyzZH4G
HY9UVs4OEDkCMDOBSezB0y9ai/9doTNcaictdEBu8nfdXKoTtzrn+VX4UPrkH5hm
7NQ1fTQuj1MR7yBCmYqN3Q2Q+Efuujyx0FwBzAuy1aKYuwIDAQABo3UwczAdBgNV
HQ4EFgQUG+dK5Uos08QUwAWofDb3a8YcYlIwHwYDVR0jBBgwFoAUG+dK5Uos08QU
wAWofDb3a8YcYlIwDwYDVR0TAQH/BAUwAwEB/zAgBgNVHREEGTAXggM6OjGHEAAA
AAAAAAAAAAAAAAAAAAEwDQYJKoZIhvcNAQELBQADgYEAjT767TDq6q4lOextf3tZ
BjeuYDUy7bb1fDBAN5rBT1ywr7r0JE6/KOnsZx4jbevx3MllxNpx0gOM2bgwJlnG
8tgwRB6pxDyln01WBj9b5ymK60jdkw7gg3yYpqEs5/VBQidFO3BmDqf5cGO8PU7p
0VWdfJBP2UbwblNXdImI1zk=
-----END CERTIFICATE-----
5 changes: 5 additions & 0 deletions dummyserver/server.py
Expand Up @@ -58,11 +58,16 @@
'certfile': os.path.join(CERTS_PATH, 'server.ipv6addr.crt'),
'keyfile': os.path.join(CERTS_PATH, 'server.ipv6addr.key'),
}
IPV6_SAN_CERTS = {
'certfile': os.path.join(CERTS_PATH, 'server.ipv6_san.crt'),
'keyfile': DEFAULT_CERTS['keyfile']
}
DEFAULT_CA = os.path.join(CERTS_PATH, 'cacert.pem')
DEFAULT_CA_BAD = os.path.join(CERTS_PATH, 'client_bad.pem')
NO_SAN_CA = os.path.join(CERTS_PATH, 'cacert.no_san.pem')
DEFAULT_CA_DIR = os.path.join(CERTS_PATH, 'ca_path_test')
IPV6_ADDR_CA = os.path.join(CERTS_PATH, 'server.ipv6addr.crt')
IPV6_SAN_CA = os.path.join(CERTS_PATH, 'server.ipv6_san.crt')
COMBINED_CERT_AND_KEY = os.path.join(CERTS_PATH, 'server.combined.pem')


Expand Down
2 changes: 1 addition & 1 deletion src/urllib3/__init__.py
Expand Up @@ -27,7 +27,7 @@

__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
__license__ = 'MIT'
__version__ = '1.24.1'
__version__ = '1.24.2'

__all__ = (
'HTTPConnectionPool',
Expand Down
3 changes: 3 additions & 0 deletions src/urllib3/contrib/pyopenssl.py
Expand Up @@ -184,6 +184,9 @@ def idna_encode(name):
except idna.core.IDNAError:
return None

if ':' in name:
return name

name = idna_encode(name)
if name is None:
return None
Expand Down
7 changes: 5 additions & 2 deletions src/urllib3/poolmanager.py
Expand Up @@ -7,6 +7,7 @@
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
from .connectionpool import port_by_scheme
from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown
from .packages import six
from .packages.six.moves.urllib.parse import urljoin
from .request import RequestMethods
from .util.url import parse_url
Expand Down Expand Up @@ -342,8 +343,10 @@ def urlopen(self, method, url, redirect=True, **kw):
# conn.is_same_host() which may use socket.gethostbyname() in the future.
if (retries.remove_headers_on_redirect
and not conn.is_same_host(redirect_location)):
for header in retries.remove_headers_on_redirect:
kw['headers'].pop(header, None)
headers = list(six.iterkeys(kw['headers']))
for header in headers:
if header.lower() in retries.remove_headers_on_redirect:
kw['headers'].pop(header, None)

try:
retries = retries.increment(method, url, response=response, _pool=conn)
Expand Down
3 changes: 2 additions & 1 deletion src/urllib3/util/retry.py
Expand Up @@ -179,7 +179,8 @@ def __init__(self, total=10, connect=None, read=None, redirect=None, status=None
self.raise_on_status = raise_on_status
self.history = history or tuple()
self.respect_retry_after_header = respect_retry_after_header
self.remove_headers_on_redirect = remove_headers_on_redirect
self.remove_headers_on_redirect = frozenset([
h.lower() for h in remove_headers_on_redirect])

def new(self, **kw):
params = dict(
Expand Down
5 changes: 4 additions & 1 deletion src/urllib3/util/ssl_.py
Expand Up @@ -327,7 +327,10 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
if e.errno == errno.ENOENT:
raise SSLError(e)
raise
elif getattr(context, 'load_default_certs', None) is not None:

# Don't load system certs unless there were no CA certs or
# SSLContext object specified manually.
elif ssl_context is None and hasattr(context, 'load_default_certs'):
# try to load OS default certs; works well on Windows (require Python3.4+)
context.load_default_certs()

Expand Down
5 changes: 4 additions & 1 deletion test/contrib/test_pyopenssl.py
Expand Up @@ -31,7 +31,10 @@ def teardown_module():
pass


from ..with_dummyserver.test_https import TestHTTPS, TestHTTPS_TLSv1 # noqa: F401
from ..with_dummyserver.test_https import ( # noqa: F401
TestHTTPS, TestHTTPS_TLSv1, TestHTTPS_IPv6Addr,
TestHTTPS_IPSAN, TestHTTPS_NoSAN, TestHTTPS_IPV6SAN
)
from ..with_dummyserver.test_socketlevel import ( # noqa: F401
TestSNI, TestSocketClosing, TestClientCerts
)
Expand Down
6 changes: 3 additions & 3 deletions test/test_retry.py
Expand Up @@ -253,9 +253,9 @@ def test_retry_method_not_in_whitelist(self):
def test_retry_default_remove_headers_on_redirect(self):
retry = Retry()

assert list(retry.remove_headers_on_redirect) == ['Authorization']
assert list(retry.remove_headers_on_redirect) == ['authorization']

def test_retry_set_remove_headers_on_redirect(self):
retry = Retry(remove_headers_on_redirect=['X-API-Secret'])
retry = Retry(remove_headers_on_redirect=['x-api-secret'])

assert list(retry.remove_headers_on_redirect) == ['X-API-Secret']
assert list(retry.remove_headers_on_redirect) == ['x-api-secret']
37 changes: 37 additions & 0 deletions test/test_ssl.py
Expand Up @@ -88,3 +88,40 @@ def test_create_urllib3_context_set_ciphers(monkeypatch, ciphers, expected_ciphe

assert context.set_ciphers.call_count == 1
assert context.set_ciphers.call_args == mock.call(expected_ciphers)


def test_wrap_socket_given_context_no_load_default_certs():
context = mock.create_autospec(ssl_.SSLContext)
context.load_default_certs = mock.Mock()

sock = mock.Mock()
ssl_.ssl_wrap_socket(sock, ssl_context=context)

context.load_default_certs.assert_not_called()


def test_wrap_socket_given_ca_certs_no_load_default_certs(monkeypatch):
context = mock.create_autospec(ssl_.SSLContext)
context.load_default_certs = mock.Mock()
context.options = 0

monkeypatch.setattr(ssl_, "SSLContext", lambda *_, **__: context)

sock = mock.Mock()
ssl_.ssl_wrap_socket(sock, ca_certs="/tmp/fake-file")

context.load_default_certs.assert_not_called()
context.load_verify_locations.assert_called_with("/tmp/fake-file", None)


def test_wrap_socket_default_loads_default_certs(monkeypatch):
context = mock.create_autospec(ssl_.SSLContext)
context.load_default_certs = mock.Mock()
context.options = 0

monkeypatch.setattr(ssl_, "SSLContext", lambda *_, **__: context)

sock = mock.Mock()
ssl_.ssl_wrap_socket(sock)

context.load_default_certs.assert_called_with()
20 changes: 19 additions & 1 deletion test/with_dummyserver/test_https.py
Expand Up @@ -17,7 +17,7 @@
DEFAULT_CLIENT_NO_INTERMEDIATE_CERTS,
NO_SAN_CERTS, NO_SAN_CA, DEFAULT_CA_DIR,
IPV6_ADDR_CERTS, IPV6_ADDR_CA, HAS_IPV6,
IP_SAN_CERTS)
IP_SAN_CERTS, IPV6_SAN_CA, IPV6_SAN_CERTS)

from test import (
onlyPy279OrNewer,
Expand Down Expand Up @@ -625,5 +625,23 @@ def test_strip_square_brackets_before_validating(self):
self.assertEqual(r.status, 200)


class TestHTTPS_IPV6SAN(IPV6HTTPSDummyServerTestCase):
certs = IPV6_SAN_CERTS

def test_can_validate_ipv6_san(self):
"""Ensure that urllib3 can validate SANs with IPv6 addresses in them."""
try:
import ipaddress # noqa: F401
except ImportError:
pytest.skip("Only runs on systems with an ipaddress module")

https_pool = HTTPSConnectionPool('[::1]', self.port,
cert_reqs='CERT_REQUIRED',
ca_certs=IPV6_SAN_CA)
self.addCleanup(https_pool.close)
r = https_pool.request('GET', '/')
self.assertEqual(r.status, 200)


if __name__ == '__main__':
unittest.main()
26 changes: 26 additions & 0 deletions test/with_dummyserver/test_poolmanager.py
Expand Up @@ -123,6 +123,17 @@ def test_redirect_cross_host_remove_headers(self):

self.assertNotIn('Authorization', data)

r = http.request('GET', '%s/redirect' % self.base_url,
fields={'target': '%s/headers' % self.base_url_alt},
headers={'authorization': 'foo'})

self.assertEqual(r.status, 200)

data = json.loads(r.data.decode('utf-8'))

self.assertNotIn('authorization', data)
self.assertNotIn('Authorization', data)

def test_redirect_cross_host_no_remove_headers(self):
http = PoolManager()
self.addCleanup(http.clear)
Expand Down Expand Up @@ -155,6 +166,21 @@ def test_redirect_cross_host_set_removed_headers(self):
self.assertNotIn('X-API-Secret', data)
self.assertEqual(data['Authorization'], 'bar')

r = http.request('GET', '%s/redirect' % self.base_url,
fields={'target': '%s/headers' % self.base_url_alt},
headers={'x-api-secret': 'foo',
'authorization': 'bar'},
retries=Retry(remove_headers_on_redirect=['X-API-Secret']))

self.assertEqual(r.status, 200)

data = json.loads(r.data.decode('utf-8'))

self.assertNotIn('x-api-secret', data)
self.assertNotIn('X-API-Secret', data)

self.assertEqual(data['Authorization'], 'bar')

def test_raise_on_redirect(self):
http = PoolManager()
self.addCleanup(http.clear)
Expand Down