1use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7use crate::client::{BucketOperations, OSSClientInner};
8use crate::error::{ErrorContext, OssError, OssErrorKind, Result};
9use crate::http::client::HttpRequest;
10use crate::types::bucket::BucketName;
11
12#[derive(Debug, Clone)]
13pub struct ServerSideEncryptionConfiguration {
14 pub sse_algorithm: String,
15 pub kms_master_key_id: Option<String>,
16}
17
18#[derive(Debug, Clone, Serialize)]
19#[serde(rename = "ServerSideEncryptionRule")]
20struct SSEConfig {
21 #[serde(rename = "ApplyServerSideEncryptionByDefault")]
22 apply: SSEApply,
23}
24
25#[derive(Debug, Clone, Serialize)]
26struct SSEApply {
27 #[serde(rename = "SSEAlgorithm")]
28 sse_algorithm: String,
29 #[serde(rename = "KMSMasterKeyID", skip_serializing_if = "Option::is_none")]
30 kms_master_key_id: Option<String>,
31}
32
33#[derive(Debug, Clone, Deserialize)]
34#[serde(rename = "ServerSideEncryptionRule")]
35struct SSEConfigResponse {
36 #[serde(rename = "ApplyServerSideEncryptionByDefault")]
37 apply: SSEApplyResponse,
38}
39
40#[derive(Debug, Clone, Deserialize)]
41struct SSEApplyResponse {
42 #[serde(rename = "SSEAlgorithm")]
43 sse_algorithm: String,
44 #[serde(rename = "KMSMasterKeyID", default)]
45 kms_master_key_id: Option<String>,
46}
47
48pub struct PutBucketEncryptionBuilder {
49 client: Arc<OSSClientInner>,
50 bucket: BucketName,
51 config: ServerSideEncryptionConfiguration,
52}
53
54impl PutBucketEncryptionBuilder {
55 pub(crate) fn new(
56 client: Arc<OSSClientInner>,
57 bucket: BucketName,
58 config: ServerSideEncryptionConfiguration,
59 ) -> Self {
60 Self {
61 client,
62 bucket,
63 config,
64 }
65 }
66
67 pub async fn send(self) -> Result<PutBucketEncryptionOutput> {
68 let endpoint = self.client.endpoint.clone();
69 let uri = format!("https://{}.{}?encryption", self.bucket.as_str(), endpoint);
70
71 let query_params: Vec<(String, String)> = vec![("encryption".into(), String::new())];
72
73 let config = SSEConfig {
74 apply: SSEApply {
75 sse_algorithm: self.config.sse_algorithm,
76 kms_master_key_id: self.config.kms_master_key_id,
77 },
78 };
79
80 let body_xml = crate::util::xml::to_xml(&config)?;
81
82 let request = HttpRequest::builder()
83 .method(http::Method::PUT)
84 .uri(&uri)
85 .body(bytes::Bytes::from(body_xml))
86 .build();
87
88 let response = self
89 .client
90 .send_signed(request, Some(&self.bucket), query_params)
91 .await
92 .map_err(|e| OssError {
93 kind: OssErrorKind::TransportError,
94 context: Box::new(ErrorContext {
95 operation: Some("PutBucketEncryption".into()),
96 bucket: Some(self.bucket.to_string()),
97 endpoint: Some(endpoint),
98 ..Default::default()
99 }),
100 source: Some(Box::new(e)),
101 })?;
102
103 if response.status().is_success() {
104 Ok(PutBucketEncryptionOutput {
105 request_id: response
106 .headers
107 .get("x-oss-request-id")
108 .and_then(|v| v.to_str().ok())
109 .unwrap_or("")
110 .to_string(),
111 })
112 } else {
113 Err(OssError {
114 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
115 status_code: response.status().as_u16(),
116 code: String::new(),
117 message: String::new(),
118 request_id: String::new(),
119 host_id: String::new(),
120 resource: Some(self.bucket.to_string()),
121 string_to_sign: None,
122 })),
123 context: Box::new(ErrorContext {
124 operation: Some("PutBucketEncryption".into()),
125 bucket: Some(self.bucket.to_string()),
126 ..Default::default()
127 }),
128 source: None,
129 })
130 }
131 }
132}
133
134#[derive(Debug, Clone)]
135pub struct PutBucketEncryptionOutput {
136 pub request_id: String,
137}
138
139pub struct GetBucketEncryptionBuilder {
140 client: Arc<OSSClientInner>,
141 bucket: BucketName,
142}
143
144impl GetBucketEncryptionBuilder {
145 pub(crate) fn new(client: Arc<OSSClientInner>, bucket: BucketName) -> Self {
146 Self { client, bucket }
147 }
148
149 pub async fn send(self) -> Result<GetBucketEncryptionOutput> {
150 let endpoint = self.client.endpoint.clone();
151 let uri = format!("https://{}.{}?encryption", self.bucket.as_str(), endpoint);
152
153 let query_params: Vec<(String, String)> = vec![("encryption".into(), String::new())];
154
155 let request = HttpRequest::builder()
156 .method(http::Method::GET)
157 .uri(&uri)
158 .build();
159
160 let response = self
161 .client
162 .send_signed(request, Some(&self.bucket), query_params)
163 .await
164 .map_err(|e| OssError {
165 kind: OssErrorKind::TransportError,
166 context: Box::new(ErrorContext {
167 operation: Some("GetBucketEncryption".into()),
168 bucket: Some(self.bucket.to_string()),
169 endpoint: Some(endpoint),
170 ..Default::default()
171 }),
172 source: Some(Box::new(e)),
173 })?;
174
175 if response.is_success() {
176 let body_str = response.body_as_str().unwrap_or("");
177 let config: SSEConfigResponse =
178 crate::util::xml::from_xml(body_str).map_err(|e| OssError {
179 kind: OssErrorKind::DeserializationError,
180 context: Box::new(ErrorContext {
181 operation: Some("GetBucketEncryption: parse XML".into()),
182 bucket: Some(self.bucket.to_string()),
183 ..Default::default()
184 }),
185 source: Some(Box::new(e)),
186 })?;
187
188 Ok(GetBucketEncryptionOutput {
189 sse_algorithm: config.apply.sse_algorithm,
190 kms_master_key_id: config.apply.kms_master_key_id,
191 })
192 } else {
193 Err(OssError {
194 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
195 status_code: response.status().as_u16(),
196 code: String::new(),
197 message: String::new(),
198 request_id: String::new(),
199 host_id: String::new(),
200 resource: Some(self.bucket.to_string()),
201 string_to_sign: None,
202 })),
203 context: Box::new(ErrorContext {
204 operation: Some("GetBucketEncryption".into()),
205 bucket: Some(self.bucket.to_string()),
206 ..Default::default()
207 }),
208 source: None,
209 })
210 }
211 }
212}
213
214#[derive(Debug, Clone)]
215pub struct GetBucketEncryptionOutput {
216 pub sse_algorithm: String,
217 pub kms_master_key_id: Option<String>,
218}
219
220pub struct DeleteBucketEncryptionBuilder {
221 client: Arc<OSSClientInner>,
222 bucket: BucketName,
223}
224
225impl DeleteBucketEncryptionBuilder {
226 pub(crate) fn new(client: Arc<OSSClientInner>, bucket: BucketName) -> Self {
227 Self { client, bucket }
228 }
229
230 pub async fn send(self) -> Result<DeleteBucketEncryptionOutput> {
231 let endpoint = self.client.endpoint.clone();
232 let uri = format!("https://{}.{}?encryption", self.bucket.as_str(), endpoint);
233
234 let query_params: Vec<(String, String)> = vec![("encryption".into(), String::new())];
235
236 let request = HttpRequest::builder()
237 .method(http::Method::DELETE)
238 .uri(&uri)
239 .build();
240
241 let response = self
242 .client
243 .send_signed(request, Some(&self.bucket), query_params)
244 .await
245 .map_err(|e| OssError {
246 kind: OssErrorKind::TransportError,
247 context: Box::new(ErrorContext {
248 operation: Some("DeleteBucketEncryption".into()),
249 bucket: Some(self.bucket.to_string()),
250 endpoint: Some(endpoint),
251 ..Default::default()
252 }),
253 source: Some(Box::new(e)),
254 })?;
255
256 if response.status().is_success() {
257 Ok(DeleteBucketEncryptionOutput {
258 request_id: response
259 .headers
260 .get("x-oss-request-id")
261 .and_then(|v| v.to_str().ok())
262 .unwrap_or("")
263 .to_string(),
264 })
265 } else {
266 Err(OssError {
267 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
268 status_code: response.status().as_u16(),
269 code: String::new(),
270 message: String::new(),
271 request_id: String::new(),
272 host_id: String::new(),
273 resource: Some(self.bucket.to_string()),
274 string_to_sign: None,
275 })),
276 context: Box::new(ErrorContext {
277 operation: Some("DeleteBucketEncryption".into()),
278 bucket: Some(self.bucket.to_string()),
279 ..Default::default()
280 }),
281 source: None,
282 })
283 }
284 }
285}
286
287#[derive(Debug, Clone)]
288pub struct DeleteBucketEncryptionOutput {
289 pub request_id: String,
290}
291
292impl BucketOperations {
293 pub fn put_encryption(
294 &self,
295 config: ServerSideEncryptionConfiguration,
296 ) -> PutBucketEncryptionBuilder {
297 PutBucketEncryptionBuilder::new(
298 self.client_inner().clone(),
299 self.bucket_name().clone(),
300 config,
301 )
302 }
303
304 pub fn get_encryption(&self) -> GetBucketEncryptionBuilder {
305 GetBucketEncryptionBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
306 }
307
308 pub fn delete_encryption(&self) -> DeleteBucketEncryptionBuilder {
309 DeleteBucketEncryptionBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use std::sync::Mutex;
316
317 use crate::client::OSSClientInner;
318 use crate::config::credentials::Credentials;
319 use crate::http::client::{HttpClient, HttpRequest, HttpResponse};
320 use crate::types::region::Region;
321
322 use super::*;
323
324 struct RecordingHttpClient {
325 requests: Arc<Mutex<Vec<HttpRequest>>>,
326 status_code: http::StatusCode,
327 response_body: bytes::Bytes,
328 }
329
330 #[async_trait::async_trait]
331 impl HttpClient for RecordingHttpClient {
332 async fn send(&self, request: HttpRequest) -> crate::error::Result<HttpResponse> {
333 self.requests.lock().unwrap().push(request);
334 let mut headers = http::HeaderMap::new();
335 headers.insert(
336 "x-oss-request-id",
337 http::HeaderValue::from_static("rid-encryption"),
338 );
339 Ok(HttpResponse {
340 status: self.status_code,
341 headers,
342 body: self.response_body.clone(),
343 })
344 }
345 }
346
347 fn create_test_inner_with_body(
348 status: http::StatusCode,
349 body: bytes::Bytes,
350 ) -> (Arc<OSSClientInner>, Arc<Mutex<Vec<HttpRequest>>>) {
351 let requests = Arc::new(Mutex::new(Vec::new()));
352 let http = Arc::new(RecordingHttpClient {
353 requests: requests.clone(),
354 status_code: status,
355 response_body: body,
356 });
357 let credentials = Arc::new(crate::config::credentials::StaticCredentialsProvider::new(
358 Credentials::builder()
359 .access_key_id("test-ak")
360 .access_key_secret("test-sk")
361 .build()
362 .unwrap(),
363 ));
364 let inner = Arc::new(OSSClientInner {
365 http,
366 credentials,
367 signer: Arc::from(crate::signer::create_signer(crate::signer::SignVersion::V4)),
368 region: Region::CnHangzhou,
369 endpoint: "oss-cn-hangzhou.aliyuncs.com".into(),
370 });
371 (inner, requests)
372 }
373
374 #[test]
375 fn encryption_xml_aes256() {
376 let config = SSEConfig {
377 apply: SSEApply {
378 sse_algorithm: "AES256".into(),
379 kms_master_key_id: None,
380 },
381 };
382 let xml = crate::util::xml::to_xml(&config).unwrap();
383 assert!(xml.contains("<SSEAlgorithm>AES256</SSEAlgorithm>"));
384 assert!(!xml.contains("KMSMasterKeyID"));
385 }
386
387 #[test]
388 fn encryption_xml_kms() {
389 let config = SSEConfig {
390 apply: SSEApply {
391 sse_algorithm: "KMS".into(),
392 kms_master_key_id: Some("key-id-123".into()),
393 },
394 };
395 let xml = crate::util::xml::to_xml(&config).unwrap();
396 assert!(xml.contains("<SSEAlgorithm>KMS</SSEAlgorithm>"));
397 assert!(xml.contains("<KMSMasterKeyID>key-id-123</KMSMasterKeyID>"));
398 }
399
400 #[tokio::test]
401 async fn get_bucket_encryption_parses_xml() {
402 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
403<ServerSideEncryptionRule>
404 <ApplyServerSideEncryptionByDefault>
405 <SSEAlgorithm>KMS</SSEAlgorithm>
406 <KMSMasterKeyID>key-id</KMSMasterKeyID>
407 </ApplyServerSideEncryptionByDefault>
408</ServerSideEncryptionRule>"#;
409 let (inner, _) = create_test_inner_with_body(http::StatusCode::OK, bytes::Bytes::from(xml));
410 let builder =
411 GetBucketEncryptionBuilder::new(inner, BucketName::new("test-bucket").unwrap());
412 let output = builder.send().await.unwrap();
413 assert_eq!(output.sse_algorithm, "KMS");
414 assert_eq!(output.kms_master_key_id.as_deref(), Some("key-id"));
415 }
416
417 #[tokio::test]
418 async fn delete_bucket_encryption_sends_delete_request() {
419 let (inner, requests) =
420 create_test_inner_with_body(http::StatusCode::NO_CONTENT, bytes::Bytes::new());
421 let builder =
422 DeleteBucketEncryptionBuilder::new(inner, BucketName::new("test-bucket").unwrap());
423 builder.send().await.unwrap();
424 let captured = requests.lock().unwrap();
425 assert_eq!(captured[0].method, http::Method::DELETE);
426 }
427}