diff --git a/moto/core/models.py b/moto/core/models.py index ffb2ffd9f..8ca74d5b5 100644 --- a/moto/core/models.py +++ b/moto/core/models.py @@ -7,6 +7,7 @@ import inspect import os import re import six +import types from io import BytesIO from collections import defaultdict from botocore.handlers import BUILTIN_HANDLERS @@ -217,12 +218,29 @@ botocore_mock = responses.RequestsMock( assert_all_requests_are_fired=False, target="botocore.vendored.requests.adapters.HTTPAdapter.send", ) + responses_mock = responses._default_mock # Add passthrough to allow any other requests to work # Since this uses .startswith, it applies to http and https requests. responses_mock.add_passthru("http") +def _find_first_match(self, request): + for i, match in enumerate(self._matches): + if match.matches(request): + return match + + return None + + +# Modify behaviour of the matcher to only/always return the first match +# Default behaviour is to return subsequent matches for subsequent requests, which leads to https://github.com/spulec/moto/issues/2567 +# - First request matches on the appropriate S3 URL +# - Same request, executed again, will be matched on the subsequent match, which happens to be the catch-all, not-yet-implemented, callback +# Fix: Always return the first match +responses_mock._find_match = types.MethodType(_find_first_match, responses_mock) + + BOTOCORE_HTTP_METHODS = ["GET", "DELETE", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"] diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 2193f8b27..48655ee17 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -39,6 +39,7 @@ import moto.s3.models as s3model from moto.core.exceptions import InvalidNextTokenException from moto.core.utils import py2_strip_unicode_keys + if settings.TEST_SERVER_MODE: REDUCED_PART_SIZE = s3model.UPLOAD_PART_MIN_SIZE EXPECTED_ETAG = '"140f92a6df9f9e415f74a1463bcee9bb-2"' @@ -1018,12 +1019,23 @@ def test_s3_object_in_public_bucket(): s3_anonymous.Object(key="file.txt", bucket_name="test-bucket").get() exc.exception.response["Error"]["Code"].should.equal("403") + +@mock_s3 +def test_s3_object_in_public_bucket_using_multiple_presigned_urls(): + s3 = boto3.resource("s3") + bucket = s3.Bucket("test-bucket") + bucket.create( + ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "us-west-1"} + ) + bucket.put_object(Body=b"ABCD", Key="file.txt") + params = {"Bucket": "test-bucket", "Key": "file.txt"} presigned_url = boto3.client("s3").generate_presigned_url( "get_object", params, ExpiresIn=900 ) - response = requests.get(presigned_url) - assert response.status_code == 200 + for i in range(1, 10): + response = requests.get(presigned_url) + assert response.status_code == 200, "Failed on req number {}".format(i) @mock_s3