Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions s3/src/bucket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1111,6 +1111,123 @@ impl Bucket {
request.response_data(false).await
}

/// Retrieves the bucket policy as a JSON string.
///
/// # Example:
///
/// ```rust,no_run
/// use s3::bucket::Bucket;
/// use s3::creds::Credentials;
/// use anyhow::Result;
///
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
///
/// let bucket_name = "rust-s3-test";
/// let region = "us-east-1".parse()?;
/// let credentials = Credentials::default()?;
/// let bucket = Bucket::new(bucket_name, region, credentials)?;
///
/// // Async variant with `tokio` or `async-std` features
/// let policy = bucket.get_bucket_policy().await?;
///
/// // `sync` feature will produce an identical method
/// #[cfg(feature = "sync")]
/// let policy = bucket.get_bucket_policy()?;
///
/// // Blocking variant, generated with `blocking` feature in combination
/// // with `tokio` or `async-std` features.
/// #[cfg(feature = "blocking")]
/// let policy = bucket.get_bucket_policy_blocking()?;
/// # Ok(())
/// # }
/// ```
#[maybe_async::maybe_async]
pub async fn get_bucket_policy(&self) -> Result<String, S3Error> {
let request = RequestImpl::new(self, "", Command::GetBucketPolicy).await?;
let response = request.response_data(false).await?;
Ok(response.as_str()?.to_string())
Comment on lines +1146 to +1149

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle non-2xx before returning policy text.

Line 1149 currently returns Ok(...) even when S3 responds with an error status, which can surface error bodies as successful policy results.

💡 Suggested fix
 #[maybe_async::maybe_async]
 pub async fn get_bucket_policy(&self) -> Result<String, S3Error> {
     let request = RequestImpl::new(self, "", Command::GetBucketPolicy).await?;
     let response = request.response_data(false).await?;
-    Ok(response.as_str()?.to_string())
+    if response.status_code() >= 300 {
+        return Err(error_from_response_data(response)?);
+    }
+    Ok(response.to_string()?)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@s3/src/bucket.rs` around lines 1146 - 1149, The get_bucket_policy function
currently returns the response body as Ok even when S3 returned a non-2xx
status; update get_bucket_policy to inspect the response status after calling
RequestImpl::new(...) and response_data(false).await and return an Err(S3Error)
when the HTTP status is not successful instead of returning the body; use the
response status/methods on the object returned by response_data (or the
RequestImpl response wrapper) to detect non-2xx and convert it into an
appropriate S3Error before returning, leaving the happy-path behavior (returning
the policy string) unchanged.

}

/// Sets the bucket policy from a JSON string.
///
/// # Example:
///
/// ```rust,no_run
/// use s3::bucket::Bucket;
/// use s3::creds::Credentials;
/// use anyhow::Result;
///
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
///
/// let bucket_name = "rust-s3-test";
/// let region = "us-east-1".parse()?;
/// let credentials = Credentials::default()?;
/// let bucket = Bucket::new(bucket_name, region, credentials)?;
///
/// let policy = r#"{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":"*","Action":"s3:GetObject","Resource":"arn:aws:s3:::rust-s3-test/*"}]}"#;
///
/// // Async variant with `tokio` or `async-std` features
/// let response = bucket.put_bucket_policy(policy).await?;
///
/// // `sync` feature will produce an identical method
/// #[cfg(feature = "sync")]
/// let response = bucket.put_bucket_policy(policy)?;
///
/// // Blocking variant, generated with `blocking` feature in combination
/// // with `tokio` or `async-std` features.
/// #[cfg(feature = "blocking")]
/// let response = bucket.put_bucket_policy_blocking(policy)?;
/// # Ok(())
/// # }
/// ```
#[maybe_async::maybe_async]
pub async fn put_bucket_policy(&self, policy: &str) -> Result<ResponseData, S3Error> {
let command = Command::PutBucketPolicy {
policy: policy.to_string(),
};
let request = RequestImpl::new(self, "", command).await?;
request.response_data(false).await
}

/// Deletes the bucket policy.
///
/// # Example:
///
/// ```rust,no_run
/// use s3::bucket::Bucket;
/// use s3::creds::Credentials;
/// use anyhow::Result;
///
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
///
/// let bucket_name = "rust-s3-test";
/// let region = "us-east-1".parse()?;
/// let credentials = Credentials::default()?;
/// let bucket = Bucket::new(bucket_name, region, credentials)?;
///
/// // Async variant with `tokio` or `async-std` features
/// let response = bucket.delete_bucket_policy().await?;
///
/// // `sync` feature will produce an identical method
/// #[cfg(feature = "sync")]
/// let response = bucket.delete_bucket_policy()?;
///
/// // Blocking variant, generated with `blocking` feature in combination
/// // with `tokio` or `async-std` features.
/// #[cfg(feature = "blocking")]
/// let response = bucket.delete_bucket_policy_blocking()?;
/// # Ok(())
/// # }
/// ```
#[maybe_async::maybe_async]
pub async fn delete_bucket_policy(&self) -> Result<ResponseData, S3Error> {
let request = RequestImpl::new(self, "", Command::DeleteBucketPolicy).await?;
request.response_data(false).await
}

/// Gets torrent from an S3 path.
///
/// # Example:
Expand Down
25 changes: 23 additions & 2 deletions s3/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ pub enum Command<'a> {
configuration: BucketLifecycleConfiguration,
},
DeleteBucketLifecycle,
GetBucketPolicy,
PutBucketPolicy {
policy: String,
},
DeleteBucketPolicy,
GetObjectAttributes {
expected_bucket_owner: String,
version_id: Option<String>,
Expand All @@ -190,6 +195,7 @@ impl<'a> Command<'a> {
| Command::GetBucketLocation
| Command::GetObjectTagging
| Command::GetBucketLifecycle
| Command::GetBucketPolicy
| Command::ListMultipartUploads { .. }
| Command::PresignGet { .. } => HttpMethod::Get,
Command::PutObject { .. }
Expand All @@ -199,14 +205,16 @@ impl<'a> Command<'a> {
| Command::UploadPart { .. }
| Command::PutBucketCors { .. }
| Command::CreateBucket { .. }
| Command::PutBucketLifecycle { .. } => HttpMethod::Put,
| Command::PutBucketLifecycle { .. }
| Command::PutBucketPolicy { .. } => HttpMethod::Put,
Command::DeleteObject
| Command::DeleteObjectTagging
| Command::AbortMultipartUpload { .. }
| Command::PresignDelete { .. }
| Command::DeleteBucket
| Command::DeleteBucketCors { .. }
| Command::DeleteBucketLifecycle => HttpMethod::Delete,
| Command::DeleteBucketLifecycle
| Command::DeleteBucketPolicy => HttpMethod::Delete,
Command::InitiateMultipartUpload { .. }
| Command::CompleteMultipartUpload { .. }
| Command::DeleteObjects { .. } => HttpMethod::Post,
Expand Down Expand Up @@ -255,6 +263,9 @@ impl<'a> Command<'a> {
Command::DeleteBucketCors { .. } => 0,
Command::GetBucketLifecycle => 0,
Command::DeleteBucketLifecycle { .. } => 0,
Command::GetBucketPolicy => 0,
Command::PutBucketPolicy { policy } => policy.len(),
Command::DeleteBucketPolicy => 0,
Command::GetObjectAttributes { .. } => 0,
Command::DeleteObjects { data } => data.len(),
};
Expand Down Expand Up @@ -289,6 +300,9 @@ impl<'a> Command<'a> {
Command::DeleteBucketCors { .. } => "text/plain".into(),
Command::GetBucketLifecycle => "text/plain".into(),
Command::DeleteBucketLifecycle { .. } => "text/plain".into(),
Command::GetBucketPolicy => "text/plain".into(),
Command::PutBucketPolicy { .. } => "application/json".into(),
Command::DeleteBucketPolicy => "text/plain".into(),
Command::CopyObject { .. } => "text/plain".into(),
Command::PutObjectTagging { .. } => "text/plain".into(),
Command::UploadPart { .. } => "text/plain".into(),
Expand Down Expand Up @@ -355,6 +369,13 @@ impl<'a> Command<'a> {
Command::DeleteBucketCors { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::GetBucketLifecycle => EMPTY_PAYLOAD_SHA.into(),
Command::DeleteBucketLifecycle { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::GetBucketPolicy => EMPTY_PAYLOAD_SHA.into(),
Command::PutBucketPolicy { policy } => {
let mut sha = Sha256::default();
sha.update(policy.as_bytes());
hex::encode(sha.finalize().as_slice())
}
Command::DeleteBucketPolicy => EMPTY_PAYLOAD_SHA.into(),
Command::CopyObject { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::UploadPart { .. } => EMPTY_PAYLOAD_SHA.into(),
Command::InitiateMultipartUpload { .. } => EMPTY_PAYLOAD_SHA.into(),
Expand Down
7 changes: 7 additions & 0 deletions s3/src/request/request_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ pub trait Request {
} else if let Command::PutBucketCors { configuration, .. } = &self.command() {
let cors = configuration.to_string();
cors.as_bytes().to_vec()
} else if let Command::PutBucketPolicy { policy } = &self.command() {
policy.as_bytes().to_vec()
} else if let Command::DeleteObjects { data } = &self.command() {
data.to_string().as_bytes().to_vec()
} else {
Expand Down Expand Up @@ -552,6 +554,11 @@ pub trait Request {
| Command::DeleteBucketCors { .. } => {
url_str.push_str("?cors");
}
Command::GetBucketPolicy
| Command::PutBucketPolicy { .. }
| Command::DeleteBucketPolicy => {
url_str.push_str("?policy");
}
Command::GetObjectAttributes { version_id, .. } => {
if let Some(version_id) = version_id {
url_str.push_str(&format!("?attributes&versionId={}", version_id));
Expand Down