8000 fix pre-signed POST str type for content-length-range (#10889) · localstack/localstack@4473f89 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4473f89

Browse files
authored
fix pre-signed POST str type for content-length-range (#10889)
1 parent 9aba238 commit 4473f89

File tree

4 files changed

+66
-26
lines changed

4 files changed

+66
-26
lines changed

localstack-core/localstack/services/s3/presigned_url.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,8 +790,9 @@ def validate_post_policy(
790790
form_dict = {k.lower(): v for k, v in request_form.items()}
791791
for condition in conditions:
792792
if not _verify_condition(condition, form_dict, additional_policy_metadata):
793+
str_condition = str(condition).replace("'", '"')
793794
raise AccessDenied(
794-
f"Invalid according to Policy: Policy Condition failed: {condition}",
795+
f"Invalid according to Policy: Policy Condition failed: {str_condition}",
795796
HostId=FAKE_HOST_ID,
796797
)
797798

@@ -829,6 +830,11 @@ def _verify_condition(condition: list | dict, form: dict, additional_policy_meta
829830

830831
case ["content-length-range", start, end]:
831832
size = additional_policy_metadata.get("content_length", 0)
833+
try:
834+
start, end = int(start), int(end)
835+
except ValueError:
836+
return False
837+
832838
if size < start:
833839
raise EntityTooSmall(
834840
"Your proposed upload is smaller than the minimum allowed size",

tests/aws/services/s3/test_s3.py

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -10140,13 +10140,6 @@ def post_generated_presigned_post_with_default_file(
1014010140
allow_redirects=False,
1014110141
)
1014210142

10143-
@staticmethod
10144-
def parse_response_xml(content: bytes) -> dict:
10145-
if not is_aws_cloud():
10146-
# AWS use double quotes in error messages and LocalStack uses single. Try to unify before snapshotting
10147-
content = content.replace(b"'", b'"')
10148-
return xmltodict.parse(content)
10149-
1015010143
@markers.aws.validated
1015110144
@pytest.mark.xfail(
1015210145
reason="failing sporadically with new HTTP gateway (only in CI)",
@@ -10763,8 +10756,7 @@ def test_post_object_policy_conditions_validation_eq(self, s3_bucket, aws_client
1076310756

1076410757
# assert that it's rejected
1076510758
assert response.status_code == 403
10766-
resp_content = self.parse_response_xml(response.content)
10767-
snapshot.match("invalid-condition-eq", resp_content)
10759+
snapshot.match("invalid-condition-eq", xmltodict.parse(response.content))
1076810760

1076910761
# PostObject with a wrong condition (missing $ prefix)
1077010762
presigned_request = aws_client.s3.generate_presigned_post(
@@ -10781,8 +10773,7 @@ def test_post_object_policy_conditions_validation_eq(self, s3_bucket, aws_client
1078110773

1078210774
# assert that it's rejected
1078310775
assert response.status_code == 403
10784-
resp_content = self.parse_response_xml(response.content)
10785-
snapshot.match("invalid-condition-missing-prefix", resp_content)
10776+
snapshot.match("invalid-condition-missing-prefix", xmltodict.parse(response.content))
1078610777

1078710778
# PostObject with a wrong condition (multiple condition in one dict)
1078810779
presigned_request = aws_client.s3.generate_presigned_post(
@@ -10799,8 +10790,7 @@ def test_post_object_policy_conditions_validation_eq(self, s3_bucket, aws_client
1079910790

1080010791
# assert that it's rejected
1080110792
assert response.status_code == 400
10802-
resp_content = self.parse_response_xml(response.content)
10803-
snapshot.match("invalid-condition-wrong-condition", resp_content)
10793+
snapshot.match("invalid-condition-wrong-condition", xmltodict.parse(response.content))
1080410794

1080510795
# PostObject with a wrong condition value casing
1080610796
presigned_request = aws_client.s3.generate_presigned_post(
@@ -10815,8 +10805,7 @@ def test_post_object_policy_conditions_validation_eq(self, s3_bucket, aws_client
1081510805
response = self.post_generated_presigned_post_with_default_file(presigned_request)
1081610806
# assert that it's rejected
1081710807
assert response.status_code == 403
10818-
resp_content = self.parse_response_xml(response.content)
10819-
snapshot.match("invalid-condition-wrong-value-casing", resp_content)
10808+
snapshot.match("invalid-condition-wrong-value-casing", xmltodict.parse(response.content))
1082010809

1082110810
object_expires = rfc_1123_datetime(
1082210811
datetime.datetime.now(ZoneInfo("GMT")) + datetime.timedelta(minutes=10)
@@ -10948,17 +10937,15 @@ def test_post_object_policy_conditions_validation_starts_with(
1094810937

1094910938
# assert that it's rejected
1095010939
assert response.status_code == 403
10951-
resp_content = self.parse_response_xml(response.content)
10952-
snapshot.match("invalid-condition-starts-with", resp_content)
10940+
snapshot.match("invalid-condition-starts-with", xmltodict.parse(response.content))
1095310941

1095410942
# PostObject with a right redirect location start but wrong casing
1095510943
presigned_request["fields"]["success_action_redirect"] = "HTTP://localhost.test/random"
1095610944
response = self.post_generated_presigned_post_with_default_file(presigned_request)
1095710945

1095810946
# assert that it's rejected
1095910947
assert response.status_code == 403
10960-
resp_content = self.parse_response_xml(response.content)
10961-
snapshot.match("invalid-condition-starts-with-casing", resp_content)
10948+
snapshot.match("invalid-condition-starts-with-casing", xmltodict.parse(response.content))
1096210949

1096310950
# PostObject with a right redirect location start
1096410951
presigned_request["fields"]["success_action_redirect"] = redirect_location
@@ -11079,6 +11066,45 @@ def test_post_object_policy_validation_size(self, s3_bucket, aws_client, snapsho
1107911066
final_object = aws_client.s3.get_object(Bucket=s3_bucket, Key=object_key)
1108011067
snapshot.match("final-object", final_object)
1108111068

11069+
# try with string values for the content length range
11070+
presigned_request = aws_client.s3.generate_presigned_post(
11071+
Bucket=s3_bucket,
11072+
Key=object_key,
11073+
ExpiresIn=60,
11074+
Conditions=[
11075+
{"bucket": s3_bucket},
11076+
["content-length-range", "5", "10"],
11077+
],
11078+
)
11079+
# PostObject with a body length of 10
11080+
response = requests.post(
11081+
presigned_request["url"],
11082+
data=presigned_request["fields"],
11083+
files={"file": "a" * 10},
11084+
verify=False,
11085+
)
11086+
assert response.status_code == 204
11087+
11088+
# try with string values that are not cast-able for the content length range
11089+
presigned_request = aws_client. F438 s3.generate_presigned_post(
11090+
Bucket=s3_bucket,
11091+
Key=object_key,
11092+
ExpiresIn=60,
11093+
Conditions=[
11094+
{"bucket": s3_bucket},
11095+
["content-length-range", "test", "10"],
11096+
],
11097+
)
11098+
# PostObject with a body length of 10
11099+
response = requests.post(
11100+
presigned_request["url"],
11101+
data=presigned_request["fields"],
11102+
files={"file": "a" * 10},
11103+
verify=False,
11104+
)
11105+
assert response.status_code == 403
11106+
snapshot.match("invalid-content-length-wrong-type", xmltodict.parse(response.content))
11107+
1108211108
@pytest.mark.skipif(
1108311109
condition=TEST_S3_IMAGE or LEGACY_V2_S3_PROVIDER,
1108411110
reason="STS not enabled in S3 image / moto does not implement this",

tests/aws/services/s3/test_s3.snapshot.json

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11526,7 +11526,7 @@
1152611526
}
1152711527
},
1152811528
"tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_validation_size": {
11529-
"recorded-date": "08-04-2024, 17:29:34",
11529+
"recorded-date": "24-05-2024, 11:43:48",
1153011530
"recorded-content": {
1153111531
"invalid-content-length-too-big": {
1153211532
"Error": {
@@ -11561,11 +11561,19 @@
1156111561
"HTTPHeaders": {},
1156211562
"HTTPStatusCode": 200
1156311563
}
11564+
},
11565+
"invalid-content-length-wrong-type": {
11566+
"Error": {
11567+
"Code": "AccessDenied",
11568+
"HostId": "<host-id>",
11569+
"Message": "Invalid according to Policy: Policy Condition failed: [\"content-length-range\", \"test\", \"10\"]",
11570+
"RequestId": "<request-id:4>"
11571+
}
1156411572
}
1156511573
}
1156611574
},
1156711575
"tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_eq": {
11568-
"recorded-date": "21-05-2024, 16:51:17",
11576+
"recorded-date": "24-05-2024, 15:10:58",
1156911577
"recorded-content": {
1157011578
"invalid-condition-eq": {
1157111579
"Error": {
@@ -11633,7 +11641,7 @@
1163311641
}
1163411642
},
1163511643
"tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_starts_with": {
11636-
"recorded-date": "10-04-2024, 12:16:01",
11644+
"recorded-date": "24-05-2024, 15:11:14",
1163711645
"recorded-content": {
1163811646
"invalid-condition-starts-with": {
1163911647
"Error": {

tests/aws/services/s3/test_s3.validation.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -522,13 +522,13 @@
522522
"last_validated_date": "2023-08-09T15:58:37+00:00"
523523
},
524524
"tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_eq": {
525-
"last_validated_date": "2024-05-21T16:51:17+00:00"
525+
"last_validated_date": "2024-05-24T15:10:58+00:00"
526526
},
527527
"tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_conditions_validation_starts_with": {
528-
"last_validated_date": "2024-04-10T12:16:01+00:00"
528+
"last_validated_date": "2024-05-24T15:11:14+00:00"
529529
},
530530
"tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_policy_validation_size": {
531-
"last_validated_date": "2024-04-08T17:29:34+00:00"
531+
"last_validated_date": "2024-05-24T11:43:48+00:00"
532532
},
533533
"tests/aws/services/s3/test_s3.py::TestS3PresignedPost::test_post_object_with_file_as_string": {
534534
"last_validated_date": "2024-02-24T01:01:59+00:00"

0 commit comments

Comments
 (0)
0