Skip to content

Commit

Permalink
Merge pull request #169 from bendemaree/appengine-insecure-transport
Browse files Browse the repository at this point in the history
Disable cert validation when monkeypatching AppEngine
  • Loading branch information
sigmavirus24 committed Dec 13, 2016
2 parents 7679220 + 1936c4e commit 41f36f9
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 5 deletions.
23 changes: 22 additions & 1 deletion docs/adapters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,27 @@ ways to take advantage of this support at the moment:
appengine.monkeypatch()
.. _insecure_appengine:

If you should need to disable certificate validation when monkeypatching (to
force third-party libraries that use Requests to not validate certificates, if
they do not provide API surface to do so, for example), you can disable it:

.. code-block:: python
from requests_toolbelt.adapters import appengine
appengine.monkeypatch(validate_certificate=False)
.. warning::

If ``validate_certificate`` is ``False``, the monkeypatched adapter
will *not* validate certificates. This effectively sets the
``validate_certificate`` argument to urlfetch.Fetch() to ``False``. You
should avoid using this wherever possible. Details can be found in the
`documentation for urlfetch.Fetch()`_.

.. _documentation for urlfetch.Fetch(): https://cloud.google.com/appengine/docs/python/refdocs/google.appengine.api.urlfetch

.. autoclass:: requests_toolbelt.adapters.appengine.AppEngineAdapter

FingerprintAdapter
Expand Down Expand Up @@ -131,7 +152,7 @@ SourceAddressAdapter

.. versionadded:: 0.3.0

The :class:`~requests_toolbelt.adapters.source.SourceAddressAdapter` allows a
The :class:`~requests_toolbelt.adapters.source.SourceAddressAdapter` allows a
user to specify a source address for their connnection.

.. autoclass:: requests_toolbelt.adapters.source.SourceAddressAdapter
Expand Down
45 changes: 42 additions & 3 deletions requests_toolbelt/adapters/appengine.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@
>>> appengine.monkeypatch()
which will ensure all requests.Session objects use AppEngineAdapter properly.
You are also able to :ref:`disable certificate validation <insecure_appengine>`
when monkey-patching.
"""
import requests
import warnings
from requests import adapters
from requests import sessions

Expand Down Expand Up @@ -59,6 +63,32 @@ def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = _AppEnginePoolManager(self._validate_certificate)


class InsecureAppEngineAdapter(AppEngineAdapter):
"""An always-insecure GAE adapter for Requests.
This is a variant of the the transport adapter for Requests to use
urllib3's GAE support that does not validate certificates. Use with
caution!
.. note::
The ``validate_certificate`` keyword argument will not be honored here
and is not part of the signature because we always force it to
``False``.
See :class:`AppEngineAdapter` for further details.
"""

def __init__(self, *args, **kwargs):
if kwargs.pop("validate_certificate", False):
warnings.warn("Certificate validation cannot be specified on the "
"InsecureAppEngineAdapter, but was present. This "
"will be ignored and certificate validation will "
"remain off.", exc.IgnoringGAECertificateValidation)

super(InsecureAppEngineAdapter, self).__init__(
validate_certificate=False, *args, **kwargs)


class _AppEnginePoolManager(object):
"""Implements urllib3's PoolManager API expected by requests.
Expand Down Expand Up @@ -123,19 +153,28 @@ def urlopen(self, method, url, body=None, headers=None, retries=None,
**response_kw)


def monkeypatch():
def monkeypatch(validate_certificate=True):
"""Sets up all Sessions to use AppEngineAdapter by default.
If you don't want to deal with configuring your own Sessions,
or if you use libraries that use requests directly (ie requests.post),
then you may prefer to monkeypatch and auto-configure all Sessions.
.. warning: :
If ``validate_certificate`` is ``False``, certification validation will
effectively be disabled for all requests.
"""
_check_version()
# HACK: We should consider modifying urllib3 to support this cleanly,
# so that we can set a module-level variable in the sessions module,
# instead of overriding an imported HTTPAdapter as is done here.
sessions.HTTPAdapter = AppEngineAdapter
adapters.HTTPAdapter = AppEngineAdapter
adapter = AppEngineAdapter
if not validate_certificate:
adapter = InsecureAppEngineAdapter

sessions.HTTPAdapter = adapter
adapters.HTTPAdapter = adapter


def _check_version():
Expand Down
14 changes: 13 additions & 1 deletion requests_toolbelt/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,21 @@ class VersionMismatchError(Exception):


class RequestsVersionTooOld(Warning):
"""Used to indiciate that the Requests version is too old.
"""Used to indicate that the Requests version is too old.
If the version of Requests is too old to support a feature, we will issue
this warning to the user.
"""
pass


class IgnoringGAECertificateValidation(Warning):
"""Used to indicate that given GAE validation behavior will be ignored.
If the user has tried to specify certificate validation when using the
insecure AppEngine adapter, it will be ignored (certificate validation will
remain off), so we will issue this warning to the user.
In :class:`requests_toolbelt.adapters.appengine.InsecureAppEngineAdapter`.
"""
pass
45 changes: 45 additions & 0 deletions tests/test_appengine_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import pytest
import requests

from requests_toolbelt import exceptions as exc

REQUESTS_SUPPORTS_GAE = requests.__build__ >= 0x021000

if REQUESTS_SUPPORTS_GAE:
Expand Down Expand Up @@ -42,3 +44,46 @@ def test_get(mock_urlfetch):
assert args == ('http://url/',)
assert kwargs['deadline'] == 9
assert kwargs['headers']['Foo'] == 'bar'


@pytest.mark.skipif(sys.version_info >= (3,),
reason="App Engine doesn't support Python 3 (yet) and "
"urllib3's appengine contrib code is Python 2 "
"only. Until the latter changes, this test will "
"be skipped, unfortunately.")
@pytest.mark.skipif(not REQUESTS_SUPPORTS_GAE,
reason="Requires Requests v2.10.0 or later")
def test_appengine_monkeypatch():
"""Tests monkeypatching Requests adapters for AppEngine compatibility.
"""
adapter = requests.sessions.HTTPAdapter

appengine.monkeypatch()

assert requests.sessions.HTTPAdapter == appengine.AppEngineAdapter
assert requests.adapters.HTTPAdapter == appengine.AppEngineAdapter

appengine.monkeypatch(validate_certificate=False)

assert requests.sessions.HTTPAdapter == appengine.InsecureAppEngineAdapter
assert requests.adapters.HTTPAdapter == appengine.InsecureAppEngineAdapter

requests.sessions.HTTPAdapter = adapter
requests.adapters.HTTPAdapter = adapter


@pytest.mark.skipif(sys.version_info >= (3,),
reason="App Engine doesn't support Python 3 (yet) and "
"urllib3's appengine contrib code is Python 2 "
"only. Until the latter changes, this test will "
"be skipped, unfortunately.")
@pytest.mark.skipif(not REQUESTS_SUPPORTS_GAE,
reason="Requires Requests v2.10.0 or later")
@mock.patch.object(urllib3_appeng, 'urlfetch')
def test_insecure_appengine_adapter(mock_urlfetch):
adapter = appengine.InsecureAppEngineAdapter()

assert not adapter._validate_certificate

with pytest.warns(exc.IgnoringGAECertificateValidation):
adapter = appengine.InsecureAppEngineAdapter(validate_certificate=True)

0 comments on commit 41f36f9

Please sign in to comment.