diff --git a/docs/changelog.rst b/docs/changelog.rst index 1ba2d1d548..7bd3fa1a3b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -27,6 +27,7 @@ In preparation for HD Views developments (PR #3298) - Recreate cache folders if missing. (#3384) - Modify site's geometry before saving to avoid edition and export of shapefiles (#3399) +- Fix API V2 cache key with X-Forwarded-Proto header (#3404) 2.94.0 (2022-12-12) diff --git a/geotrek/api/tests/test_v2.py b/geotrek/api/tests/test_v2.py index 544a37e7bc..b715ce3f72 100644 --- a/geotrek/api/tests/test_v2.py +++ b/geotrek/api/tests/test_v2.py @@ -13,6 +13,7 @@ from django.utils import timezone from freezegun.api import freeze_time from mapentity.tests.factories import SuperUserFactory +from rest_framework.test import APITestCase from geotrek import __version__ from geotrek.authent import models as authent_models @@ -38,6 +39,7 @@ from geotrek.tourism.tests import factories as tourism_factory from geotrek.trekking import models as trek_models from geotrek.trekking.tests import factories as trek_factory +from geotrek.trekking.tests.factories import PracticeFactory from geotrek.zoning import models as zoning_models from geotrek.zoning.tests import factories as zoning_factory @@ -4255,3 +4257,41 @@ def test_cache_is_used_when_getting_trek_profile_svg(self): response = self.client.get(reverse('apiv2:trek-profile', args=(self.trek.pk,)), {"format": "svg"}) self.assertEqual(response.status_code, 200) self.assertIn('image/svg+xml', response['Content-Type']) + + +class GenericCacheTestCase(APITestCase): + @classmethod + def setUpTestData(cls): + cls.practice = PracticeFactory.create() + + def test_cache_invalidates_along_x_forwarded_proto_header(self): + with self.assertNumQueries(2): + response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,))) + data = response.json() + self.assertTrue(data['pictogram'].startswith('http://')) + + # after cache hit, query number is 1 + with self.assertNumQueries(1): + response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,))) + data = response.json() + self.assertTrue(data['pictogram'].startswith('http://')) + + # we used custom header, cache is invalidate and url is now https + with self.assertNumQueries(2): + response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,)), + HTTP_X_FORWARDED_PROTO='https') + data = response.json() + self.assertTrue(data['pictogram'].startswith('https://')) + + # cache is hit + with self.assertNumQueries(1): + response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,)), + HTTP_X_FORWARDED_PROTO='https') + data = response.json() + self.assertTrue(data['pictogram'].startswith('https://')) + + # first request is always cached + with self.assertNumQueries(1): + response = self.client.get(reverse('apiv2:practice-detail', args=(self.practice.pk,))) + data = response.json() + self.assertTrue(data['pictogram'].startswith('http://')) diff --git a/geotrek/api/v2/viewsets.py b/geotrek/api/v2/viewsets.py index 7f897a347a..2f43c4205c 100644 --- a/geotrek/api/v2/viewsets.py +++ b/geotrek/api/v2/viewsets.py @@ -33,7 +33,8 @@ def get_ordered_query_params(self): def get_base_cache_string(self): """ return cache string as url path + ordered query params """ - return f"{self.request.path}:{self.get_ordered_query_params()}:{self.request.accepted_renderer.format}" + proto_scheme = self.request.headers.get('X-Forwarded-Proto', self.request.scheme) # take care about scheme defined in nginx.conf + return f"{self.request.path}:{self.get_ordered_query_params()}:{self.request.accepted_renderer.format}:{proto_scheme}" def get_object_cache_key(self, pk): """ return specific object cache key based on object date_update column"""