S3: head_object() should only return 405 on DeleteMarkers when specifying a Versionid (#6922)
This commit is contained in:
parent
1f51c2f766
commit
069218e1f3
@ -1,6 +1,10 @@
|
|||||||
from typing import Any, Optional, Union
|
from typing import Any, Optional, Union, TYPE_CHECKING
|
||||||
from moto.core.exceptions import RESTError
|
from moto.core.exceptions import RESTError
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from moto.s3.models import FakeDeleteMarker
|
||||||
|
|
||||||
|
|
||||||
ERROR_WITH_BUCKET_NAME = """{% extends 'single_error' %}
|
ERROR_WITH_BUCKET_NAME = """{% extends 'single_error' %}
|
||||||
{% block extra %}<BucketName>{{ bucket }}</BucketName>{% endblock %}
|
{% block extra %}<BucketName>{{ bucket }}</BucketName>{% endblock %}
|
||||||
"""
|
"""
|
||||||
@ -561,8 +565,8 @@ class ObjectLockConfigurationNotFoundError(S3ClientError):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MethodNotAllowed(S3ClientError):
|
class HeadOnDeleteMarker(Exception):
|
||||||
code = 405
|
"""Marker to indicate that we've called `head_object()` on a FakeDeleteMarker"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self, marker: "FakeDeleteMarker"):
|
||||||
super().__init__("MethodNotAllowed", "Method Not Allowed")
|
self.marker = marker
|
||||||
|
@ -42,7 +42,7 @@ from moto.s3.exceptions import (
|
|||||||
MissingKey,
|
MissingKey,
|
||||||
InvalidNotificationDestination,
|
InvalidNotificationDestination,
|
||||||
MalformedXML,
|
MalformedXML,
|
||||||
MethodNotAllowed,
|
HeadOnDeleteMarker,
|
||||||
InvalidStorageClass,
|
InvalidStorageClass,
|
||||||
InvalidTargetBucketForLogging,
|
InvalidTargetBucketForLogging,
|
||||||
CrossLocationLoggingProhibitted,
|
CrossLocationLoggingProhibitted,
|
||||||
@ -2107,7 +2107,7 @@ class S3Backend(BaseBackend, CloudWatchMetricProvider):
|
|||||||
bucket_name, key_name, version_id, part_number, return_delete_marker=True
|
bucket_name, key_name, version_id, part_number, return_delete_marker=True
|
||||||
)
|
)
|
||||||
if isinstance(obj, FakeDeleteMarker):
|
if isinstance(obj, FakeDeleteMarker):
|
||||||
raise MethodNotAllowed
|
raise HeadOnDeleteMarker(obj)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def get_object_acl(self, key: FakeKey) -> Optional[FakeAcl]:
|
def get_object_acl(self, key: FakeKey) -> Optional[FakeAcl]:
|
||||||
|
@ -34,7 +34,7 @@ from .exceptions import (
|
|||||||
InvalidContentMD5,
|
InvalidContentMD5,
|
||||||
InvalidContinuationToken,
|
InvalidContinuationToken,
|
||||||
S3ClientError,
|
S3ClientError,
|
||||||
MethodNotAllowed,
|
HeadOnDeleteMarker,
|
||||||
MissingBucket,
|
MissingBucket,
|
||||||
MissingKey,
|
MissingKey,
|
||||||
MissingVersion,
|
MissingVersion,
|
||||||
@ -1766,14 +1766,18 @@ class S3Response(BaseResponse):
|
|||||||
key = self.backend.head_object(
|
key = self.backend.head_object(
|
||||||
bucket_name, key_name, version_id=version_id, part_number=part_number
|
bucket_name, key_name, version_id=version_id, part_number=part_number
|
||||||
)
|
)
|
||||||
except MethodNotAllowed:
|
except HeadOnDeleteMarker as exc:
|
||||||
headers = {
|
headers = {
|
||||||
"x-amz-delete-marker": "true",
|
"x-amz-delete-marker": "true",
|
||||||
"x-amz-version-id": version_id,
|
"x-amz-version-id": version_id,
|
||||||
"allow": "DELETE",
|
|
||||||
"content-type": "application/xml",
|
"content-type": "application/xml",
|
||||||
}
|
}
|
||||||
return 405, headers, "Method Not Allowed"
|
if version_id:
|
||||||
|
headers["allow"] = "DELETE"
|
||||||
|
return 405, headers, "Method Not Allowed"
|
||||||
|
else:
|
||||||
|
headers["x-amz-version-id"] = exc.marker.version_id
|
||||||
|
return 404, headers, "Not Found"
|
||||||
if key:
|
if key:
|
||||||
response_headers.update(key.metadata)
|
response_headers.update(key.metadata)
|
||||||
response_headers.update(key.response_dict)
|
response_headers.update(key.response_dict)
|
||||||
|
@ -1708,7 +1708,7 @@ def test_delete_versioned_bucket_returns_metadata(name=None):
|
|||||||
|
|
||||||
# list_object_versions returns the object itself, and a DeleteMarker
|
# list_object_versions returns the object itself, and a DeleteMarker
|
||||||
# object.head() returns a 'x-amz-delete-marker' header
|
# object.head() returns a 'x-amz-delete-marker' header
|
||||||
# delete_marker.head() returns a 405
|
# delete_marker_version.head() returns a 405
|
||||||
for version in versions.filter(Prefix="test1"):
|
for version in versions.filter(Prefix="test1"):
|
||||||
if version.version_id == deleted_version_id:
|
if version.version_id == deleted_version_id:
|
||||||
with pytest.raises(ClientError) as exc:
|
with pytest.raises(ClientError) as exc:
|
||||||
@ -1723,6 +1723,18 @@ def test_delete_versioned_bucket_returns_metadata(name=None):
|
|||||||
else:
|
else:
|
||||||
assert version.head()["ResponseMetadata"]["HTTPStatusCode"] == 200
|
assert version.head()["ResponseMetadata"]["HTTPStatusCode"] == 200
|
||||||
|
|
||||||
|
# Note that delete_marker.head() returns a regular 404
|
||||||
|
# i.e., without specifying the versionId
|
||||||
|
with pytest.raises(ClientError) as exc:
|
||||||
|
client.head_object(Bucket=name, Key="test1")
|
||||||
|
err = exc.value.response
|
||||||
|
assert err["Error"] == {"Code": "404", "Message": "Not Found"}
|
||||||
|
assert err["ResponseMetadata"]["HTTPStatusCode"] == 404
|
||||||
|
assert err["ResponseMetadata"]["HTTPHeaders"]["x-amz-delete-marker"] == "true"
|
||||||
|
assert (
|
||||||
|
err["ResponseMetadata"]["HTTPHeaders"]["x-amz-version-id"] == deleted_version_id
|
||||||
|
)
|
||||||
|
|
||||||
# Delete the same object gives a new version id
|
# Delete the same object gives a new version id
|
||||||
del_mrk1 = client.delete_object(Bucket=name, Key="test1")
|
del_mrk1 = client.delete_object(Bucket=name, Key="test1")
|
||||||
assert del_mrk1["DeleteMarker"] is True
|
assert del_mrk1["DeleteMarker"] is True
|
||||||
|
Loading…
x
Reference in New Issue
Block a user