mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-18 18:46:17 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e2f98037e |
@@ -17,6 +17,7 @@
|
||||
use crate::common::{RustFSTestEnvironment, init_logging, local_http_client};
|
||||
use async_compression::tokio::write::{BzEncoder, XzEncoder};
|
||||
use aws_sdk_s3::error::{ProvideErrorMetadata, SdkError};
|
||||
use aws_sdk_s3::operation::head_object::HeadObjectOutput;
|
||||
use aws_sdk_s3::primitives::ByteStream;
|
||||
use aws_sdk_s3::types::{
|
||||
ServerSideEncryption, ServerSideEncryptionByDefault, ServerSideEncryptionConfiguration, ServerSideEncryptionRule,
|
||||
@@ -349,6 +350,71 @@ async fn run_post_object_policy_case(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One accepted POST Object upload driven end-to-end (backlog#1838): starts a
|
||||
/// fresh server, allows anonymous PutObject on `bucket`, posts an anonymous
|
||||
/// POST Object form whose policy carries `policy_conditions` and whose form
|
||||
/// carries `form_field` on top of the mandatory key+policy fields, then asserts
|
||||
/// 204 with an empty body, that `read_stored` observes the submitted value on
|
||||
/// the stored object, and that the object body round-tripped unchanged.
|
||||
/// `case` prefixes every assertion message so a failing table row is
|
||||
/// identifiable at a glance.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn run_post_object_accept_case(
|
||||
bucket: &str,
|
||||
object_key: &str,
|
||||
policy_conditions: Vec<serde_json::Value>,
|
||||
form_field: (&str, &str),
|
||||
file_mime: &str,
|
||||
file_body: &[u8],
|
||||
read_stored: fn(&HeadObjectOutput) -> Option<&str>,
|
||||
case: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
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(policy_conditions);
|
||||
|
||||
let (field_name, field_value) = form_field;
|
||||
let post_form = reqwest::multipart::Form::new()
|
||||
.text("key", object_key.to_string())
|
||||
.text("policy", policy)
|
||||
.text(field_name.to_string(), field_value.to_string())
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(file_body.to_vec())
|
||||
.file_name("upload.txt")
|
||||
.mime_str(file_mime)?,
|
||||
);
|
||||
|
||||
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::NO_CONTENT, "[{case}] unexpected status");
|
||||
assert!(
|
||||
response_body.is_empty(),
|
||||
"[{case}] 204 response should not contain a body, got: {response_body}"
|
||||
);
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(read_stored(&head), Some(field_value), "[{case}] stored {field_name} mismatch");
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), file_body, "[{case}] uploaded body mismatch");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Table-driven fold of the nine `*_missing_from_policy_conditions` POST
|
||||
/// Object tests (backlog#1838 PR1). Every row keeps its original test's exact
|
||||
/// bucket, key, form field, file body, and expected error strings; the shared
|
||||
@@ -1551,60 +1617,6 @@ async fn test_anonymous_post_object_accepts_sse_s3_missing_from_policy_condition
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_storage_class_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-storage-class";
|
||||
let object_key = "post-storage-class-object.txt";
|
||||
let expected_body = b"post-storage-class-body".to_vec();
|
||||
let storage_class = "REDUCED_REDUNDANCY";
|
||||
|
||||
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": storage_class }),
|
||||
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", storage_class)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.file_name("upload.txt")
|
||||
.mime_str("text/plain")?,
|
||||
);
|
||||
|
||||
let post_resp = local_http_client()
|
||||
.post(format!("{}/{}", env.url, bucket))
|
||||
.multipart(post_form)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
assert_eq!(post_resp.status(), reqwest::StatusCode::NO_CONTENT);
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.storage_class().map(|value| value.as_str()), Some(storage_class));
|
||||
|
||||
let uploaded = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = uploaded.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_rejects_storage_class_missing_from_policy_conditions()
|
||||
@@ -2620,521 +2632,183 @@ async fn test_anonymous_post_object_rejects_success_action_redirect_missing_from
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Table-driven fold of the eleven accepted POST Object form-field tests
|
||||
/// (backlog#1838 PR4). Every row keeps its original test's exact bucket, key,
|
||||
/// form field, submitted value, policy condition, file MIME type, and file
|
||||
/// body; the shared shape is: the policy covers the field (exact condition or
|
||||
/// `starts-with` prefix), the form submits it, the upload returns 204 with an
|
||||
/// empty body, and the stored object echoes the submitted value back.
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_metadata_field_covered_by_starts_with()
|
||||
async fn test_anonymous_post_object_accepts_fields_covered_by_policy_conditions()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
// (case, bucket, object_key, field, submitted value, `starts-with` prefix
|
||||
// (`None` pins the field to an exact policy condition), file part MIME type,
|
||||
// file body, stored-value accessor)
|
||||
type Case = (
|
||||
&'static str,
|
||||
&'static str,
|
||||
&'static str,
|
||||
&'static str,
|
||||
&'static str,
|
||||
Option<&'static str>,
|
||||
&'static str,
|
||||
&'static [u8],
|
||||
fn(&HeadObjectOutput) -> Option<&str>,
|
||||
);
|
||||
let cases: &[Case] = &[
|
||||
(
|
||||
"storage-class",
|
||||
"anon-post-storage-class",
|
||||
"post-storage-class-object.txt",
|
||||
"x-amz-storage-class",
|
||||
"REDUCED_REDUNDANCY",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-storage-class-body",
|
||||
|head: &HeadObjectOutput| head.storage_class().map(|value| value.as_str()),
|
||||
),
|
||||
(
|
||||
"metadata-starts-with",
|
||||
"anon-post-policy-meta-accept",
|
||||
"uploads/meta-object.txt",
|
||||
"x-amz-meta-project",
|
||||
"alpha-demo",
|
||||
Some("alpha-"),
|
||||
"text/plain",
|
||||
b"post-policy-meta-body",
|
||||
|head: &HeadObjectOutput| head.metadata().and_then(|meta| meta.get("project")).map(String::as_str),
|
||||
),
|
||||
(
|
||||
"content-type",
|
||||
"anon-post-policy-content-type-accept",
|
||||
"uploads/content-type-accept.txt",
|
||||
"Content-Type",
|
||||
"text/plain",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-policy-content-type-accept",
|
||||
|head: &HeadObjectOutput| head.content_type(),
|
||||
),
|
||||
(
|
||||
"content-type-starts-with",
|
||||
"anon-post-policy-content-type-accept",
|
||||
"uploads/content-type-object.txt",
|
||||
"Content-Type",
|
||||
"image/png",
|
||||
Some("image/"),
|
||||
"image/png",
|
||||
b"post-policy-content-type-body",
|
||||
|head: &HeadObjectOutput| head.content_type(),
|
||||
),
|
||||
(
|
||||
"content-disposition",
|
||||
"anon-post-policy-disposition-accept",
|
||||
"uploads/disposition-object.txt",
|
||||
"Content-Disposition",
|
||||
"attachment; filename=\"upload.txt\"",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-policy-disposition-body",
|
||||
|head: &HeadObjectOutput| head.content_disposition(),
|
||||
),
|
||||
(
|
||||
"cache-control",
|
||||
"anon-post-policy-cache-control-accept",
|
||||
"uploads/cache-control-object.txt",
|
||||
"Cache-Control",
|
||||
"max-age=60",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-policy-cache-control-body",
|
||||
|head: &HeadObjectOutput| head.cache_control(),
|
||||
),
|
||||
(
|
||||
"content-language",
|
||||
"anon-post-policy-content-language-accept",
|
||||
"uploads/content-language-object.txt",
|
||||
"Content-Language",
|
||||
"en-US",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-policy-content-language-body",
|
||||
|head: &HeadObjectOutput| head.content_language(),
|
||||
),
|
||||
(
|
||||
"content-encoding",
|
||||
"anon-post-policy-content-encoding-accept",
|
||||
"uploads/content-encoding-object.txt",
|
||||
"Content-Encoding",
|
||||
"gzip",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-policy-content-encoding-body",
|
||||
|head: &HeadObjectOutput| head.content_encoding(),
|
||||
),
|
||||
(
|
||||
"website-redirect-location",
|
||||
"anon-post-policy-website-redirect-accept",
|
||||
"uploads/website-redirect-object.txt",
|
||||
"x-amz-website-redirect-location",
|
||||
"/docs/landing.html",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-policy-website-redirect-body",
|
||||
|head: &HeadObjectOutput| head.website_redirect_location(),
|
||||
),
|
||||
(
|
||||
"expires",
|
||||
"anon-post-policy-expires-accept",
|
||||
"uploads/expires-object.txt",
|
||||
"Expires",
|
||||
"Wed, 21 Oct 2037 07:28:00 GMT",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-policy-expires-body",
|
||||
|head: &HeadObjectOutput| head.expires_string(),
|
||||
),
|
||||
(
|
||||
"metadata-exact",
|
||||
"anon-post-policy-meta-exact-accept",
|
||||
"uploads/meta-exact-accept-object.txt",
|
||||
"x-amz-meta-project",
|
||||
"alpha-demo",
|
||||
None,
|
||||
"text/plain",
|
||||
b"post-policy-meta-exact-body",
|
||||
|head: &HeadObjectOutput| head.metadata().and_then(|meta| meta.get("project")).map(String::as_str),
|
||||
),
|
||||
];
|
||||
|
||||
let bucket = "anon-post-policy-meta-accept";
|
||||
let object_key = "uploads/meta-object.txt";
|
||||
let metadata_value = "alpha-demo";
|
||||
let expected_body = b"post-policy-meta-body".to_vec();
|
||||
for (case, bucket, object_key, field, value, starts_with_prefix, file_mime, file_body, read_stored) in cases {
|
||||
let condition = match starts_with_prefix {
|
||||
Some(prefix) => serde_json::json!(["starts-with", format!("${field}"), prefix]),
|
||||
None => {
|
||||
let mut exact = serde_json::Map::new();
|
||||
exact.insert((*field).to_string(), serde_json::Value::String((*value).to_string()));
|
||||
serde_json::Value::Object(exact)
|
||||
}
|
||||
};
|
||||
|
||||
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!(["starts-with", "$x-amz-meta-project", "alpha-"]),
|
||||
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", metadata_value)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.file_name("upload.txt")
|
||||
.mime_str("text/plain")?,
|
||||
);
|
||||
|
||||
let post_resp = local_http_client()
|
||||
.post(format!("{}/{}", env.url, bucket))
|
||||
.multipart(post_form)
|
||||
.send()
|
||||
run_post_object_accept_case(
|
||||
bucket,
|
||||
object_key,
|
||||
vec![
|
||||
serde_json::json!({ "bucket": bucket }),
|
||||
serde_json::json!({ "key": object_key }),
|
||||
condition,
|
||||
serde_json::json!(["content-length-range", 0, 1024]),
|
||||
],
|
||||
(field, value),
|
||||
file_mime,
|
||||
file_body,
|
||||
*read_stored,
|
||||
case,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let status = post_resp.status();
|
||||
let response_body = post_resp.text().await?;
|
||||
|
||||
assert_eq!(status, reqwest::StatusCode::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
let metadata = head.metadata().expect("head_object should expose uploaded metadata");
|
||||
assert_eq!(metadata.get("project").map(String::as_str), Some(metadata_value));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_content_type_field_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-content-type-accept";
|
||||
let object_key = "uploads/content-type-accept.txt";
|
||||
let content_type = "text/plain";
|
||||
let expected_body = b"post-policy-content-type-accept".to_vec();
|
||||
|
||||
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": content_type }),
|
||||
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", content_type)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.file_name("upload.txt")
|
||||
.mime_str(content_type)?,
|
||||
);
|
||||
|
||||
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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.content_type(), Some(content_type));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_content_type_field_covered_by_starts_with()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-content-type-accept";
|
||||
let object_key = "uploads/content-type-object.txt";
|
||||
let content_type = "image/png";
|
||||
let expected_body = b"post-policy-content-type-body".to_vec();
|
||||
|
||||
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!(["starts-with", "$Content-Type", "image/"]),
|
||||
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", content_type)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.file_name("upload.txt")
|
||||
.mime_str(content_type)?,
|
||||
);
|
||||
|
||||
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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.content_type(), Some(content_type));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_content_disposition_field_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-disposition-accept";
|
||||
let object_key = "uploads/disposition-object.txt";
|
||||
let content_disposition = "attachment; filename=\"upload.txt\"";
|
||||
let expected_body = b"post-policy-disposition-body".to_vec();
|
||||
|
||||
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": content_disposition }),
|
||||
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", content_disposition)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.content_disposition(), Some(content_disposition));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_cache_control_field_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-cache-control-accept";
|
||||
let object_key = "uploads/cache-control-object.txt";
|
||||
let cache_control = "max-age=60";
|
||||
let expected_body = b"post-policy-cache-control-body".to_vec();
|
||||
|
||||
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": cache_control }),
|
||||
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", cache_control)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.cache_control(), Some(cache_control));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_content_language_field_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-content-language-accept";
|
||||
let object_key = "uploads/content-language-object.txt";
|
||||
let content_language = "en-US";
|
||||
let expected_body = b"post-policy-content-language-body".to_vec();
|
||||
|
||||
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": content_language }),
|
||||
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", content_language)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.content_language(), Some(content_language));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_content_encoding_field_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-content-encoding-accept";
|
||||
let object_key = "uploads/content-encoding-object.txt";
|
||||
let content_encoding = "gzip";
|
||||
let expected_body = b"post-policy-content-encoding-body".to_vec();
|
||||
|
||||
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": content_encoding }),
|
||||
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", content_encoding)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.content_encoding(), Some(content_encoding));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_website_redirect_location_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-website-redirect-accept";
|
||||
let object_key = "uploads/website-redirect-object.txt";
|
||||
let website_redirect_location = "/docs/landing.html";
|
||||
let expected_body = b"post-policy-website-redirect-body".to_vec();
|
||||
|
||||
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": website_redirect_location }),
|
||||
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", website_redirect_location)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.website_redirect_location(), Some(website_redirect_location));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_expires_field_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-expires-accept";
|
||||
let object_key = "uploads/expires-object.txt";
|
||||
let expires = "Wed, 21 Oct 2037 07:28:00 GMT";
|
||||
let expected_body = b"post-policy-expires-body".to_vec();
|
||||
|
||||
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": expires }),
|
||||
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", expires)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
assert_eq!(head.expires_string(), Some(expires));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -3491,65 +3165,6 @@ async fn test_anonymous_post_object_accepts_tagging_field_exact_policy_match()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_accepts_metadata_field_exact_policy_match()
|
||||
-> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
init_logging();
|
||||
|
||||
let mut env = RustFSTestEnvironment::new().await?;
|
||||
env.start_rustfs_server(vec![]).await?;
|
||||
|
||||
let bucket = "anon-post-policy-meta-exact-accept";
|
||||
let object_key = "uploads/meta-exact-accept-object.txt";
|
||||
let metadata_value = "alpha-demo";
|
||||
let expected_body = b"post-policy-meta-exact-body".to_vec();
|
||||
|
||||
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": metadata_value }),
|
||||
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", metadata_value)
|
||||
.part(
|
||||
"file",
|
||||
reqwest::multipart::Part::bytes(expected_body.clone())
|
||||
.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::NO_CONTENT);
|
||||
assert!(response_body.is_empty(), "204 response should not contain a body, got: {response_body}");
|
||||
|
||||
let head = admin_client.head_object().bucket(bucket).key(object_key).send().await?;
|
||||
let metadata = head.metadata().expect("head_object should expose uploaded metadata");
|
||||
assert_eq!(metadata.get("project").map(String::as_str), Some(metadata_value));
|
||||
|
||||
let get_out = admin_client.get_object().bucket(bucket).key(object_key).send().await?;
|
||||
let uploaded = get_out.body.collect().await?.into_bytes();
|
||||
assert_eq!(uploaded.as_ref(), expected_body.as_slice());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_anonymous_post_object_allows_x_ignore_fields_outside_policy_conditions()
|
||||
|
||||
@@ -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 | 85 | |
|
||||
| multipart_auth_test | 75 | |
|
||||
| multipart_storage_class_test | 3 | ✅ |
|
||||
| namespace_lock_quorum_test | 2 | |
|
||||
| negative_sigv4_test | 6 | ✅ |
|
||||
|
||||
Reference in New Issue
Block a user