From a825326edeb42deefbc8c6c58fa341e6d972de36 Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Thu, 13 Aug 2026 04:18:36 +0800 Subject: [PATCH] test(e2e): fold seven identical POST-policy exact-mismatch tests into one table (#6016) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test(e2e): fold seven identical POST-policy exact-mismatch tests into one table The seven *_policy_mismatch tests in multipart_auth_test.rs were body-identical after literal normalization: the policy pins one field to an exact value, the form sends a different value, and the upload must be rejected with 400 InvalidPolicyDocument naming the field. Each test booted its own full server. They fold into one table-driven test on the run_post_object_policy_case helper introduced by the PR1 fold. Every row keeps its original test's exact bucket, key, field name, policy value, mismatched form value, body bytes, and expected error strings — including the three rows that asserted the stronger InvalidPolicyDocument form. The pinned condition is built with an explicit serde_json::Map since the field name is now a table parameter. cargo nextest list reports 97 tests for this module; the inventory row is updated in the same diff (103 -> 97). Ref rustfs/backlog#1838 (PR2). * test(e2e): fold the remaining POST-policy duplicate groups (15 tests) (#6018) Completes the multipart_auth table-driven fold: the six remaining body-identical groups collapse onto the shared run_post_object_policy_case helper. - Seven single-field exact-mismatch tests (cache-control, expires, tagging, storage-class, content-type, success_action_status, metadata-field-exact) join the existing exact-condition mismatch table as rows — same shape as the PR2 fold. - The two object-lock mismatch tests become a two-row table (policy pins mode + retain-until-date, one form field mismatches). - The three SSE-KMS parameter mismatch tests become a three-row table (policy pins the SSE mode plus one KMS parameter, form differs). - The three SSE-KMS outside-policy tests become a three-row table pinning the distinct contract: an undeclared KMS parameter sails past policy validation and is rejected at runtime with 501 NotImplemented, not a policy error. Every row keeps its original test's exact bucket, key, field names, values, body bytes, and expected status/code strings. cargo nextest list reports 85 tests for the module; the inventory row is updated in the same diff (97 -> 85). Ref rustfs/backlog#1838 (PR3). --- crates/e2e_test/src/multipart_auth_test.rs | 1654 +++++--------------- docs/testing/e2e-suite-inventory.md | 2 +- 2 files changed, 417 insertions(+), 1239 deletions(-) diff --git a/crates/e2e_test/src/multipart_auth_test.rs b/crates/e2e_test/src/multipart_auth_test.rs index 28f2dea08..81613bf97 100644 --- a/crates/e2e_test/src/multipart_auth_test.rs +++ b/crates/e2e_test/src/multipart_auth_test.rs @@ -477,6 +477,422 @@ async fn test_anonymous_post_object_rejects_fields_missing_from_policy_condition Ok(()) } +/// Table-driven fold of the seven `*_policy_mismatch` POST Object tests +/// (backlog#1838 PR2). Every row keeps its original test's exact bucket, key, +/// policy value, mismatched form value, file body, and expected error strings; +/// the shared shape is: the policy pins the field to one exact value, the form +/// sends a different one, and the upload must be rejected with 400 +/// InvalidPolicyDocument naming the field. +#[tokio::test] +#[serial] +async fn test_anonymous_post_object_rejects_exact_condition_policy_mismatches() +-> Result<(), Box> { + init_logging(); + + // (case, bucket, object_key, field, policy value, mismatched form value, file body, expected code, expected mention) + type Case = ( + &'static str, + &'static str, + &'static str, + &'static str, + &'static str, + &'static str, + &'static [u8], + &'static str, + &'static str, + ); + let cases: &[Case] = &[ + ( + "content-disposition", + "anon-post-policy-content-disposition-reject", + "uploads/content-disposition-reject.txt", + "Content-Disposition", + "attachment; filename=\"payload.bin\"", + "inline", + b"post-policy-content-disposition-mismatch", + "InvalidPolicyDocument", + "content-disposition", + ), + ( + "content-language", + "anon-post-policy-content-language-reject", + "uploads/content-language-reject.txt", + "Content-Language", + "en-US", + "fr-FR", + b"post-policy-content-language-mismatch", + "InvalidPolicyDocument", + "content-language", + ), + ( + "content-encoding", + "anon-post-policy-content-encoding-reject", + "uploads/content-encoding-reject.txt", + "Content-Encoding", + "gzip", + "br", + b"post-policy-content-encoding-mismatch", + "InvalidPolicyDocument", + "content-encoding", + ), + ( + "website-redirect-location", + "anon-post-policy-website-redirect-reject", + "uploads/website-redirect-reject-object.txt", + "x-amz-website-redirect-location", + "/docs/landing.html", + "/docs/other.html", + b"website-redirect-mismatch", + "InvalidPolicyDocument", + "x-amz-website-redirect-location", + ), + ( + "metadata-uuid-exact", + "anon-post-policy-meta-uuid-mismatch", + "uploads/meta-uuid-mismatch.txt", + "x-amz-meta-uuid", + "14365123651274", + "151274", + b"post-policy-meta-uuid-mismatch", + "InvalidPolicyDocument", + "x-amz-meta-uuid", + ), + ( + "sigv4-algorithm", + "anon-post-policy-sigv4-algorithm-mismatch", + "uploads/sigv4-algorithm-mismatch.txt", + "x-amz-algorithm", + "AWS4-HMAC-SHA256", + "incorrect", + b"post-policy-sigv4-algorithm-mismatch", + "InvalidPolicyDocument", + "x-amz-algorithm", + ), + ( + "sigv4-credential", + "anon-post-policy-sigv4-credential-mismatch", + "uploads/sigv4-credential-mismatch.txt", + "x-amz-credential", + "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request", + "incorrect", + b"post-policy-sigv4-credential-mismatch", + "InvalidPolicyDocument", + "x-amz-credential", + ), + ( + "cache-control", + "anon-post-policy-cache-control-reject", + "uploads/cache-control-reject.txt", + "Cache-Control", + "max-age=60", + "max-age=120", + b"post-policy-cache-control-mismatch", + "InvalidPolicyDocument", + "cache-control", + ), + ( + "expires", + "anon-post-policy-expires-reject", + "uploads/expires-reject-object.txt", + "Expires", + "Wed, 21 Oct 2037 07:28:00 GMT", + "Wed, 21 Oct 2037 08:28:00 GMT", + b"post-policy-expires-mismatch", + "InvalidPolicyDocument", + "expires", + ), + ( + "tagging", + "anon-post-policy-tagging-reject", + "uploads/tagging-reject-object.txt", + "x-amz-tagging", + "project=alpha&env=test", + "project=alpha&env=prod", + b"post-policy-tagging-mismatch", + "InvalidPolicyDocument", + "x-amz-tagging", + ), + ( + "storage-class", + "anon-post-storage-class-mismatch", + "post-storage-class-mismatch-object.txt", + "x-amz-storage-class", + "STANDARD_IA", + "ONEZONE_IA", + b"post-storage-class-mismatch", + "InvalidPolicyDocument", + "storage-class", + ), + ( + "content-type", + "anon-post-policy-content-type", + "post-policy-content-type-object.txt", + "Content-Type", + "image/jpeg", + "application/octet-stream", + b"post-policy-body", + "InvalidPolicyDocument", + "content-type", + ), + ( + "success-action-status", + "anon-post-policy-status-mismatch", + "uploads/status-mismatch-object.txt", + "success_action_status", + "201", + "204", + b"post-policy-body", + "InvalidPolicyDocument", + "success_action_status", + ), + ( + "metadata-field-exact", + "anon-post-policy-meta-exact-mismatch", + "uploads/meta-exact-mismatch-object.txt", + "x-amz-meta-project", + "alpha-demo", + "beta-demo", + b"post-policy-body", + "InvalidPolicyDocument", + "x-amz-meta-project", + ), + ]; + + for (case, bucket, object_key, field, policy_value, form_value, file_body, expected_code, expected_mention) in cases { + let mut pinned_condition = serde_json::Map::new(); + pinned_condition.insert((*field).to_string(), serde_json::Value::String((*policy_value).to_string())); + + run_post_object_policy_case( + bucket, + object_key, + vec![ + serde_json::json!({ "bucket": bucket }), + serde_json::json!({ "key": object_key }), + serde_json::Value::Object(pinned_condition), + serde_json::json!(["content-length-range", 0, 1024]), + ], + &[(*field, *form_value)], + file_body, + reqwest::StatusCode::BAD_REQUEST, + expected_code, + expected_mention, + case, + ) + .await?; + } + + Ok(()) +} + +/// Table-driven fold of the two object-lock `*_policy_mismatch` tests +/// (backlog#1838 PR3): the policy pins both object-lock fields, the form sends +/// one of them with a different value, and the upload must be rejected with +/// 400 InvalidPolicyDocument naming the mismatched field. +#[tokio::test] +#[serial] +async fn test_anonymous_post_object_rejects_object_lock_policy_mismatches() -> Result<(), Box> +{ + init_logging(); + + // (case, bucket, object_key, form mode, form retain-until, file body, expected mention) + type Case = ( + &'static str, + &'static str, + &'static str, + &'static str, + &'static str, + &'static [u8], + &'static str, + ); + let cases: &[Case] = &[ + ( + "retention", + "anon-post-policy-object-lock-retention-reject", + "uploads/object-lock-retention-reject.txt", + "GOVERNANCE", + "2037-10-21T08:28:00Z", + b"post-policy-object-lock-retention-mismatch", + "x-amz-object-lock-retain-until-date", + ), + ( + "mode", + "anon-post-policy-object-lock-mode-reject", + "uploads/object-lock-mode-reject.txt", + "COMPLIANCE", + "2037-10-21T07:28:00Z", + b"post-policy-object-lock-mode-mismatch", + "x-amz-object-lock-mode", + ), + ]; + + for (case, bucket, object_key, form_mode, form_retain, file_body, expected_mention) in cases { + run_post_object_policy_case( + bucket, + object_key, + vec![ + serde_json::json!({ "bucket": bucket }), + serde_json::json!({ "key": object_key }), + serde_json::json!({ "x-amz-object-lock-mode": "GOVERNANCE" }), + serde_json::json!({ "x-amz-object-lock-retain-until-date": "2037-10-21T07:28:00Z" }), + serde_json::json!(["content-length-range", 0, 1024]), + ], + &[ + ("x-amz-object-lock-mode", *form_mode), + ("x-amz-object-lock-retain-until-date", *form_retain), + ], + file_body, + reqwest::StatusCode::BAD_REQUEST, + "InvalidPolicyDocument", + expected_mention, + case, + ) + .await?; + } + + Ok(()) +} + +/// Table-driven fold of the three SSE-KMS `*_policy_mismatch` tests +/// (backlog#1838 PR3): the policy pins the SSE mode and one KMS parameter to +/// exact values, the form sends a different parameter value, and the upload +/// must be rejected with 400 InvalidPolicyDocument naming the parameter. +#[tokio::test] +#[serial] +async fn test_anonymous_post_object_rejects_sse_kms_policy_mismatches() -> Result<(), Box> { + init_logging(); + + // (case, bucket, object_key, kms field, policy value, mismatched form value, file body, expected mention) + type Case = ( + &'static str, + &'static str, + &'static str, + &'static str, + &'static str, + &'static str, + &'static [u8], + &'static str, + ); + let cases: &[Case] = &[ + ( + "key-id", + "anon-post-sse-kms-keyid-mismatch", + "post-sse-kms-keyid-mismatch-object.txt", + "x-amz-server-side-encryption-aws-kms-key-id", + "expected-key", + "other-key", + b"post-sse-kms-keyid-mismatch-body", + "aws-kms-key-id", + ), + ( + "context", + "anon-post-sse-kms-context-mismatch", + "post-sse-kms-context-mismatch-object.txt", + "x-amz-server-side-encryption-context", + "e30=", + "eyJrIjoiYiJ9", + b"post-sse-kms-context-mismatch-body", + "server-side-encryption-context", + ), + ( + "bucket-key-enabled", + "anon-post-sse-kms-bucket-key-mismatch", + "post-sse-kms-bucket-key-mismatch-object.txt", + "x-amz-server-side-encryption-bucket-key-enabled", + "false", + "true", + b"post-sse-kms-bucket-key-mismatch-body", + "bucket-key-enabled", + ), + ]; + + for (case, bucket, object_key, field, policy_value, form_value, file_body, expected_mention) in cases { + let mut pinned_condition = serde_json::Map::new(); + pinned_condition.insert((*field).to_string(), serde_json::Value::String((*policy_value).to_string())); + + run_post_object_policy_case( + bucket, + object_key, + vec![ + serde_json::json!({ "bucket": bucket }), + serde_json::json!({ "key": object_key }), + serde_json::json!({ "x-amz-server-side-encryption": "aws:kms" }), + serde_json::Value::Object(pinned_condition), + serde_json::json!(["content-length-range", 0, 1024]), + ], + &[("x-amz-server-side-encryption", "aws:kms"), (*field, *form_value)], + file_body, + reqwest::StatusCode::BAD_REQUEST, + "InvalidPolicyDocument", + expected_mention, + case, + ) + .await?; + } + + Ok(()) +} + +/// Table-driven fold of the three SSE-KMS `*_outside_policy_conditions` tests +/// (backlog#1838 PR3): the policy pins only the SSE mode, the form smuggles +/// one extra KMS parameter the policy never declared, and the request must +/// sail past policy validation and be rejected at runtime with 501 +/// NotImplemented (SSE-KMS POST uploads are not implemented), not with a +/// policy error. +#[tokio::test] +#[serial] +async fn test_anonymous_post_object_rejects_sse_kms_params_outside_policy_conditions() +-> Result<(), Box> { + init_logging(); + + // (case, bucket, object_key, extra kms form field, file body) + type Case = (&'static str, &'static str, &'static str, (&'static str, &'static str), &'static [u8]); + let cases: &[Case] = &[ + ( + "key-id", + "anon-post-sse-kms-keyid", + "post-sse-kms-keyid-object.txt", + ("x-amz-server-side-encryption-aws-kms-key-id", "test-key"), + b"post-sse-kms-body", + ), + ( + "context", + "anon-post-sse-kms-context", + "post-sse-kms-context-object.txt", + ("x-amz-server-side-encryption-context", "e30="), + b"post-sse-kms-context-body", + ), + ( + "bucket-key-enabled", + "anon-post-sse-kms-bucket-key", + "post-sse-kms-bucket-key-object.txt", + ("x-amz-server-side-encryption-bucket-key-enabled", "true"), + b"post-sse-kms-bucket-key-body", + ), + ]; + + for (case, bucket, object_key, kms_field, file_body) in cases { + run_post_object_policy_case( + bucket, + object_key, + vec![ + serde_json::json!({ "bucket": bucket }), + serde_json::json!({ "key": object_key }), + serde_json::json!({ "x-amz-server-side-encryption": "aws:kms" }), + serde_json::json!(["content-length-range", 0, 1024]), + ], + &[("x-amz-server-side-encryption", "aws:kms"), *kms_field], + file_body, + reqwest::StatusCode::NOT_IMPLEMENTED, + "NotImplemented", + "notimplemented", + case, + ) + .await?; + } + + Ok(()) +} + #[tokio::test] #[serial] async fn test_anonymous_multipart_control_apis_require_auth() -> Result<(), Box> { @@ -815,354 +1231,6 @@ async fn test_anonymous_post_object_rejects_sse_kms() -> Result<(), Box Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-sse-kms-keyid"; - let object_key = "post-sse-kms-keyid-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-server-side-encryption": "aws:kms" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-server-side-encryption", "aws:kms") - .text("x-amz-server-side-encryption-aws-kms-key-id", "test-key") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-sse-kms-body".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - - assert_eq!( - status, - reqwest::StatusCode::NOT_IMPLEMENTED, - "SSE-KMS key id should not fail policy validation before runtime rejection" - ); - assert!( - response_body.contains("NotImplemented"), - "response should contain NotImplemented code, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_sse_kms_with_context_outside_policy_conditions() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-sse-kms-context"; - let object_key = "post-sse-kms-context-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-server-side-encryption": "aws:kms" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-server-side-encryption", "aws:kms") - .text("x-amz-server-side-encryption-context", "e30=") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-sse-kms-context-body".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - - assert_eq!( - status, - reqwest::StatusCode::NOT_IMPLEMENTED, - "SSE-KMS context should not fail policy validation before runtime rejection" - ); - assert!( - response_body.contains("NotImplemented"), - "response should contain NotImplemented code, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_sse_kms_key_id_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-sse-kms-keyid-mismatch"; - let object_key = "post-sse-kms-keyid-mismatch-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-server-side-encryption": "aws:kms" }), - serde_json::json!({ "x-amz-server-side-encryption-aws-kms-key-id": "expected-key" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-server-side-encryption", "aws:kms") - .text("x-amz-server-side-encryption-aws-kms-key-id", "other-key") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-sse-kms-keyid-mismatch-body".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!( - response_body.contains("InvalidPolicyDocument"), - "response should contain InvalidPolicyDocument code, got: {response_body}" - ); - assert!( - response_body_lower.contains("aws-kms-key-id"), - "response should mention the conflicting kms key id field, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_sse_kms_context_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-sse-kms-context-mismatch"; - let object_key = "post-sse-kms-context-mismatch-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-server-side-encryption": "aws:kms" }), - serde_json::json!({ "x-amz-server-side-encryption-context": "e30=" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-server-side-encryption", "aws:kms") - .text("x-amz-server-side-encryption-context", "eyJrIjoiYiJ9") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-sse-kms-context-mismatch-body".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!( - response_body.contains("InvalidPolicyDocument"), - "response should contain InvalidPolicyDocument code, got: {response_body}" - ); - assert!( - response_body_lower.contains("server-side-encryption-context"), - "response should mention the conflicting kms context field, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_sse_kms_with_bucket_key_enabled_outside_policy_conditions() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-sse-kms-bucket-key"; - let object_key = "post-sse-kms-bucket-key-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-server-side-encryption": "aws:kms" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-server-side-encryption", "aws:kms") - .text("x-amz-server-side-encryption-bucket-key-enabled", "true") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-sse-kms-bucket-key-body".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - - assert_eq!( - status, - reqwest::StatusCode::NOT_IMPLEMENTED, - "SSE-KMS bucket-key-enabled should not fail policy validation before runtime rejection" - ); - assert!( - response_body.contains("NotImplemented"), - "response should contain NotImplemented code, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_sse_kms_bucket_key_enabled_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-sse-kms-bucket-key-mismatch"; - let object_key = "post-sse-kms-bucket-key-mismatch-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-server-side-encryption": "aws:kms" }), - serde_json::json!({ "x-amz-server-side-encryption-bucket-key-enabled": "false" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-server-side-encryption", "aws:kms") - .text("x-amz-server-side-encryption-bucket-key-enabled", "true") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-sse-kms-bucket-key-mismatch-body".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!( - response_body.contains("InvalidPolicyDocument"), - "response should contain InvalidPolicyDocument code, got: {response_body}" - ); - assert!( - response_body_lower.contains("bucket-key-enabled"), - "response should mention the conflicting bucket-key-enabled field, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_accepts_sse_s3() -> Result<(), Box> { @@ -1588,63 +1656,6 @@ async fn test_anonymous_post_object_rejects_storage_class_missing_from_policy_co Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_storage_class_policy_mismatch() -> Result<(), Box> -{ - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-storage-class-mismatch"; - let object_key = "post-storage-class-mismatch-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-storage-class": "STANDARD_IA" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-storage-class", "ONEZONE_IA") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-storage-class-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!( - response_body.contains("InvalidPolicyDocument"), - "response should contain InvalidPolicyDocument code, got: {response_body}" - ); - assert!( - response_body_lower.contains("storage-class"), - "response should mention storage class mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_rejects_invalid_storage_class_value() -> Result<(), Box> @@ -2376,63 +2387,6 @@ async fn test_anonymous_post_object_rejects_content_length_range_violation() Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_success_action_status_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-status-mismatch"; - let object_key = "uploads/status-mismatch-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "success_action_status": "201" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("success_action_status", "204") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-body".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!( - response_body.contains("InvalidPolicyDocument"), - "response should contain InvalidPolicyDocument code, got: {response_body}" - ); - assert!( - response_body_lower.contains("success_action_status"), - "response should mention the conflicting status field, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_accepts_success_action_status_exact_policy_match() @@ -2895,60 +2849,6 @@ async fn test_anonymous_post_object_accepts_content_disposition_field_exact_poli Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_content_disposition_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-content-disposition-reject"; - let object_key = "uploads/content-disposition-reject.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "Content-Disposition": "attachment; filename=\"payload.bin\"" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("Content-Disposition", "inline") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-content-disposition-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("content-disposition"), - "response should mention content-disposition mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_accepts_cache_control_field_exact_policy_match() @@ -3007,60 +2907,6 @@ async fn test_anonymous_post_object_accepts_cache_control_field_exact_policy_mat Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_cache_control_policy_mismatch() -> Result<(), Box> -{ - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-cache-control-reject"; - let object_key = "uploads/cache-control-reject.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "Cache-Control": "max-age=60" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("Cache-Control", "max-age=120") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-cache-control-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("cache-control"), - "response should mention cache-control mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_accepts_content_language_field_exact_policy_match() @@ -3119,60 +2965,6 @@ async fn test_anonymous_post_object_accepts_content_language_field_exact_policy_ Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_content_language_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-content-language-reject"; - let object_key = "uploads/content-language-reject.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "Content-Language": "en-US" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("Content-Language", "fr-FR") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-content-language-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("content-language"), - "response should mention content-language mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_accepts_content_encoding_field_exact_policy_match() @@ -3231,60 +3023,6 @@ async fn test_anonymous_post_object_accepts_content_encoding_field_exact_policy_ Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_content_encoding_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-content-encoding-reject"; - let object_key = "uploads/content-encoding-reject.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "Content-Encoding": "gzip" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("Content-Encoding", "br") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-content-encoding-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("content-encoding"), - "response should mention content-encoding mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_accepts_website_redirect_location_exact_policy_match() @@ -3343,60 +3081,6 @@ async fn test_anonymous_post_object_accepts_website_redirect_location_exact_poli Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_website_redirect_location_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-website-redirect-reject"; - let object_key = "uploads/website-redirect-reject-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-website-redirect-location": "/docs/landing.html" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-website-redirect-location", "/docs/other.html") - .part( - "file", - reqwest::multipart::Part::bytes(b"website-redirect-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("x-amz-website-redirect-location"), - "response should mention x-amz-website-redirect-location mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_accepts_expires_field_exact_policy_match() @@ -3455,60 +3139,6 @@ async fn test_anonymous_post_object_accepts_expires_field_exact_policy_match() Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_expires_field_policy_mismatch() -> Result<(), Box> -{ - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-expires-reject"; - let object_key = "uploads/expires-reject-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "Expires": "Wed, 21 Oct 2037 07:28:00 GMT" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("Expires", "Wed, 21 Oct 2037 08:28:00 GMT") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-expires-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("expires"), - "response should mention Expires mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_rejects_object_lock_retention_without_permission() @@ -3565,128 +3195,6 @@ async fn test_anonymous_post_object_rejects_object_lock_retention_without_permis Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_object_lock_retention_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-object-lock-retention-reject"; - let object_key = "uploads/object-lock-retention-reject.txt"; - - let admin_client = env.create_s3_client(); - admin_client - .create_bucket() - .bucket(bucket) - .object_lock_enabled_for_bucket(true) - .send() - .await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-object-lock-mode": "GOVERNANCE" }), - serde_json::json!({ "x-amz-object-lock-retain-until-date": "2037-10-21T07:28:00Z" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-object-lock-mode", "GOVERNANCE") - .text("x-amz-object-lock-retain-until-date", "2037-10-21T08:28:00Z") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-object-lock-retention-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("x-amz-object-lock-retain-until-date"), - "response should mention x-amz-object-lock-retain-until-date mismatch, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_object_lock_mode_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-object-lock-mode-reject"; - let object_key = "uploads/object-lock-mode-reject.txt"; - - let admin_client = env.create_s3_client(); - admin_client - .create_bucket() - .bucket(bucket) - .object_lock_enabled_for_bucket(true) - .send() - .await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-object-lock-mode": "GOVERNANCE" }), - serde_json::json!({ "x-amz-object-lock-retain-until-date": "2037-10-21T07:28:00Z" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-object-lock-mode", "COMPLIANCE") - .text("x-amz-object-lock-retain-until-date", "2037-10-21T07:28:00Z") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-object-lock-mode-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("x-amz-object-lock-mode"), - "response should mention x-amz-object-lock-mode mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_rejects_object_lock_retention_missing_from_policy_conditions() @@ -3983,117 +3491,6 @@ async fn test_anonymous_post_object_accepts_tagging_field_exact_policy_match() Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_tagging_field_policy_mismatch() -> Result<(), Box> -{ - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-tagging-reject"; - let object_key = "uploads/tagging-reject-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-tagging": "project=alpha&env=test" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-tagging", "project=alpha&env=prod") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-tagging-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("x-amz-tagging"), - "response should mention x-amz-tagging mismatch, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_metadata_field_exact_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-meta-exact-mismatch"; - let object_key = "uploads/meta-exact-mismatch-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-meta-project": "alpha-demo" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-meta-project", "beta-demo") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-body".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!( - response_body.contains("InvalidPolicyDocument"), - "response should contain InvalidPolicyDocument code, got: {response_body}" - ); - assert!( - response_body_lower.contains("x-amz-meta-project"), - "response should mention the conflicting metadata field, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_accepts_metadata_field_exact_policy_match() @@ -4206,168 +3603,6 @@ async fn test_anonymous_post_object_allows_x_ignore_fields_outside_policy_condit Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_metadata_uuid_exact_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-meta-uuid-mismatch"; - let object_key = "uploads/meta-uuid-mismatch.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-meta-uuid": "14365123651274" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-meta-uuid", "151274") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-meta-uuid-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("x-amz-meta-uuid"), - "response should mention x-amz-meta-uuid mismatch, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_sigv4_algorithm_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-sigv4-algorithm-mismatch"; - let object_key = "uploads/sigv4-algorithm-mismatch.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-algorithm": "AWS4-HMAC-SHA256" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-algorithm", "incorrect") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-sigv4-algorithm-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("x-amz-algorithm"), - "response should mention x-amz-algorithm mismatch, got: {response_body}" - ); - - Ok(()) -} - -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_sigv4_credential_policy_mismatch() --> Result<(), Box> { - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-sigv4-credential-mismatch"; - let object_key = "uploads/sigv4-credential-mismatch.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "x-amz-credential": "KVGKMDUQ23TCZXTLTHLP/20160727/us-east-1/s3/aws4_request" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("x-amz-credential", "incorrect") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-sigv4-credential-mismatch".to_vec()) - .file_name("upload.txt") - .mime_str("text/plain")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!(response_body.contains("InvalidPolicyDocument")); - assert!( - response_body_lower.contains("x-amz-credential"), - "response should mention x-amz-credential mismatch, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_anonymous_post_object_rejects_sigv4_date_policy_mismatch() -> Result<(), Box> { @@ -4584,63 +3819,6 @@ async fn test_anonymous_post_object_rejects_extra_content_disposition_field() Ok(()) } -#[tokio::test] -#[serial] -async fn test_anonymous_post_object_rejects_content_type_policy_mismatch() -> Result<(), Box> -{ - init_logging(); - - let mut env = RustFSTestEnvironment::new().await?; - env.start_rustfs_server(vec![]).await?; - - let bucket = "anon-post-policy-content-type"; - let object_key = "post-policy-content-type-object.txt"; - - let admin_client = env.create_s3_client(); - admin_client.create_bucket().bucket(bucket).send().await?; - allow_anonymous_put_object(&admin_client, bucket).await?; - - let policy = encode_post_policy(vec![ - serde_json::json!({ "bucket": bucket }), - serde_json::json!({ "key": object_key }), - serde_json::json!({ "Content-Type": "image/jpeg" }), - serde_json::json!(["content-length-range", 0, 1024]), - ]); - - let post_form = reqwest::multipart::Form::new() - .text("key", object_key.to_string()) - .text("policy", policy) - .text("Content-Type", "application/octet-stream") - .part( - "file", - reqwest::multipart::Part::bytes(b"post-policy-body".to_vec()) - .file_name("upload.txt") - .mime_str("application/octet-stream")?, - ); - - let post_resp = local_http_client() - .post(format!("{}/{}", env.url, bucket)) - .multipart(post_form) - .send() - .await?; - - let status = post_resp.status(); - let response_body = post_resp.text().await?; - let response_body_lower = response_body.to_ascii_lowercase(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert!( - response_body.contains("InvalidPolicyDocument"), - "response should contain InvalidPolicyDocument code, got: {response_body}" - ); - assert!( - response_body_lower.contains("content-type"), - "response should mention the conflicting field, got: {response_body}" - ); - - Ok(()) -} - #[tokio::test] #[serial] async fn test_signed_put_object_extract_expands_tar_entries_with_prefix_headers() diff --git a/docs/testing/e2e-suite-inventory.md b/docs/testing/e2e-suite-inventory.md index 498328f44..160941978 100644 --- a/docs/testing/e2e-suite-inventory.md +++ b/docs/testing/e2e-suite-inventory.md @@ -63,7 +63,7 @@ | list_objects_v2_metadata_extension_test | 1 | | | list_objects_v2_pagination_test | 12 | ✅ | | mc_mirror_small_bucket_test | 1 | | -| multipart_auth_test | 103 | | +| multipart_auth_test | 85 | | | multipart_storage_class_test | 3 | ✅ | | namespace_lock_quorum_test | 2 | | | negative_sigv4_test | 6 | ✅ |