From 7b6f2ddab6a397a83399d94d4dc7c65908c7a1e3 Mon Sep 17 00:00:00 2001 From: Andri Baal Date: Thu, 14 May 2026 11:55:16 +0200 Subject: [PATCH] Added bucket policies --- s3/src/bucket.rs | 117 ++++++++++++++++++++++++++++++++ s3/src/command.rs | 25 ++++++- s3/src/request/request_trait.rs | 7 ++ 3 files changed, 147 insertions(+), 2 deletions(-) diff --git a/s3/src/bucket.rs b/s3/src/bucket.rs index e341d299c1..98344e1393 100644 --- a/s3/src/bucket.rs +++ b/s3/src/bucket.rs @@ -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 { + let request = RequestImpl::new(self, "", Command::GetBucketPolicy).await?; + let response = request.response_data(false).await?; + Ok(response.as_str()?.to_string()) + } + + /// 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 { + 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 { + let request = RequestImpl::new(self, "", Command::DeleteBucketPolicy).await?; + request.response_data(false).await + } + /// Gets torrent from an S3 path. /// /// # Example: diff --git a/s3/src/command.rs b/s3/src/command.rs index 4b57486af8..210ab50539 100644 --- a/s3/src/command.rs +++ b/s3/src/command.rs @@ -168,6 +168,11 @@ pub enum Command<'a> { configuration: BucketLifecycleConfiguration, }, DeleteBucketLifecycle, + GetBucketPolicy, + PutBucketPolicy { + policy: String, + }, + DeleteBucketPolicy, GetObjectAttributes { expected_bucket_owner: String, version_id: Option, @@ -190,6 +195,7 @@ impl<'a> Command<'a> { | Command::GetBucketLocation | Command::GetObjectTagging | Command::GetBucketLifecycle + | Command::GetBucketPolicy | Command::ListMultipartUploads { .. } | Command::PresignGet { .. } => HttpMethod::Get, Command::PutObject { .. } @@ -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, @@ -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(), }; @@ -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(), @@ -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(), diff --git a/s3/src/request/request_trait.rs b/s3/src/request/request_trait.rs index 9493504236..fc49531fc3 100644 --- a/s3/src/request/request_trait.rs +++ b/s3/src/request/request_trait.rs @@ -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 { @@ -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));