From b69b4d450413820e3f2a49e935954b680e2a1807 Mon Sep 17 00:00:00 2001 From: Emmanuel Cazenave Date: Thu, 1 Nov 2018 13:18:20 +0100 Subject: [PATCH] allow cookies usage in endpoint requests (#27654) --- passerelle/utils/__init__.py | 9 +++++++-- passerelle/views.py | 2 ++ tests/test_generic_endpoint.py | 30 ++++++++++++++++++++++++++++++ tests/test_requests.py | 25 +++++++++++++++++++++++++ tests/utils.py | 4 ++-- 5 files changed, 66 insertions(+), 4 deletions(-) diff --git a/passerelle/utils/__init__.py b/passerelle/utils/__init__.py index 815fe79..5d42c72 100644 --- a/passerelle/utils/__init__.py +++ b/passerelle/utils/__init__.py @@ -214,11 +214,16 @@ class Request(RequestSession): if 'timeout' not in kwargs: kwargs['timeout'] = settings.REQUESTS_TIMEOUT - # don't use persistent cookies - self.cookies.clear() + # persist cookie for the endpoint duration + if self.resource and hasattr(self.resource, 'cookiejar'): + self.cookies = self.resource.cookiejar response = super(Request, self).request(method, url, **kwargs) + if getattr(self, 'cookies', None) and self.resource \ + and hasattr(self.resource, 'cookiejar'): + self.resource.cookiejar = self.cookies + if method == 'GET' and cache_duration and (response.status_code // 100 == 2): cache.set(cache_key, { 'content': response.content, diff --git a/passerelle/views.py b/passerelle/views.py index 33b8ccc..6221a41 100644 --- a/passerelle/views.py +++ b/passerelle/views.py @@ -22,6 +22,7 @@ from django.utils.translation import ugettext_lazy as _ from django.utils.encoding import force_text from django.forms.models import modelform_factory from django.forms.widgets import ClearableFileInput +import requests from dateutil import parser as date_parser @@ -287,6 +288,7 @@ class GenericEndpointView(GenericConnectorMixin, SingleObjectMixin, View): def dispatch(self, request, *args, **kwargs): self.init_stuff(request, *args, **kwargs) connector = self.get_object() + connector.cookiejar = requests.cookies.cookiejar_from_dict({}) self.endpoint = None endpoints = [] for name, method in inspect.getmembers(connector): diff --git a/tests/test_generic_endpoint.py b/tests/test_generic_endpoint.py index 664ab80..196b60d 100644 --- a/tests/test_generic_endpoint.py +++ b/tests/test_generic_endpoint.py @@ -295,3 +295,33 @@ def test_endpoint_cache(app, db, monkeypatch): assert cache.get_calls == 5 assert cache.set_calls == 3 assert resp1.json_body != resp6.json_body + + +def test_endpoint_cookies(app, db, monkeypatch): + + @endpoint(methods=['get']) + def httpcall(obj, request): + headers = {'Set-Cookie': 'somecookie=somecookievalue'} + with utils.mock_url('http://some.example.org/', 'ok', headers): + response = obj.requests.get('http://some.example.org/') + cookie1 = response.request.headers.get('Cookie') + response = obj.requests.get('http://some.example.org/') + cookie2 = response.request.headers.get('Cookie') + return { + 'cookie1': cookie1, + 'cookie2': cookie2 + } + + monkeypatch.setattr(StubInvoicesConnector, 'httpcall', httpcall, raising=False) + + connector = StubInvoicesConnector(slug='fake') + connector.save() + + json_res = app.get('/stub-invoices/fake/httpcall').json + assert json_res['cookie1'] is None + assert json_res['cookie2'] == 'somecookie=somecookievalue' + # Do it a second time to test that no cookies are leaking from one call + # to the other + json_res = app.get('/stub-invoices/fake/httpcall').json + assert json_res['cookie1'] is None + assert json_res['cookie2'] == 'somecookie=somecookievalue' diff --git a/tests/test_requests.py b/tests/test_requests.py index 60bba9c..a68cf49 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -6,6 +6,7 @@ import mock from httmock import urlmatch, HTTMock, response from django.test import override_settings +import requests from passerelle.utils import Request, CaseInsensitiveDict from passerelle.utils.http_authenticators import HawkAuth @@ -327,3 +328,27 @@ def test_timeout(mocked_get, caplog, endpoint_response): assert mocked_get.call_args[1]['timeout'] == 42 Request(logger=logger).get('http://example.net/whatever', timeout=None) assert mocked_get.call_args[1]['timeout'] is None + + +def test_requests_cookies(caplog): + resource = MockResource() + resource.cookiejar = requests.cookies.cookiejar_from_dict({}) + logger = logging.getLogger('requests') + request = Request(resource=resource, logger=logger) + + headers = {'Set-Cookie': 'somecookie=somecookievalue'} + with utils.mock_url('http://some.example.org/', 'ok', headers): + response = request.get('http://some.example.org/') + assert response.cookies['somecookie'] == 'somecookievalue' + # cookies are stored on the resource + assert resource.cookiejar == response.cookies + + with utils.mock_url('http://some.example.org/', 'ok'): + # cookies are sent back + response = request.get('http://some.example.org/') + assert response.request.headers['Cookie'] == 'somecookie=somecookievalue' + + # cookies sent back even with a new Request obj + request = Request(resource=resource, logger=logger) + response = request.get('http://some.example.org/') + assert response.request.headers['Cookie'] == 'somecookie=somecookievalue' diff --git a/tests/utils.py b/tests/utils.py index 7a974f7..270021d 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -30,14 +30,14 @@ class FakedResponse(mock.Mock): return json.loads(self.content) -def mock_url(url, response): +def mock_url(url, response, headers=None): parsed = urlparse.urlparse(url) if not isinstance(response ,str): response = json.dumps(response) @httmock.urlmatch(netloc=parsed.netloc, path=parsed.path) def mocked(url, request): - return httmock.response(200, response, request=request) + return httmock.response(200, response, request=request, headers=headers) return httmock.HTTMock(mocked) -- 2.19.1