Skip to main content

rustack_s3_core/ops/
bucket_config.rs

1//! Bucket configuration operation handlers.
2//!
3//! Implements versioning, encryption, CORS, lifecycle, policy, tagging,
4//! notification, logging, public access block, ownership controls,
5//! object lock, accelerate, request payment, website, ACL, and
6//! policy status operations.
7
8use rustack_s3_model::{
9    error::S3Error,
10    input::{
11        DeleteBucketCorsInput, DeleteBucketEncryptionInput, DeleteBucketLifecycleInput,
12        DeleteBucketOwnershipControlsInput, DeleteBucketPolicyInput, DeleteBucketTaggingInput,
13        DeleteBucketWebsiteInput, DeletePublicAccessBlockInput,
14        GetBucketAccelerateConfigurationInput, GetBucketAclInput, GetBucketCorsInput,
15        GetBucketEncryptionInput, GetBucketLifecycleConfigurationInput, GetBucketLoggingInput,
16        GetBucketNotificationConfigurationInput, GetBucketOwnershipControlsInput,
17        GetBucketPolicyInput, GetBucketPolicyStatusInput, GetBucketRequestPaymentInput,
18        GetBucketTaggingInput, GetBucketVersioningInput, GetBucketWebsiteInput,
19        GetObjectLockConfigurationInput, GetPublicAccessBlockInput,
20        PutBucketAccelerateConfigurationInput, PutBucketAclInput, PutBucketCorsInput,
21        PutBucketEncryptionInput, PutBucketLifecycleConfigurationInput, PutBucketLoggingInput,
22        PutBucketNotificationConfigurationInput, PutBucketOwnershipControlsInput,
23        PutBucketPolicyInput, PutBucketRequestPaymentInput, PutBucketTaggingInput,
24        PutBucketVersioningInput, PutBucketWebsiteInput, PutObjectLockConfigurationInput,
25        PutPublicAccessBlockInput,
26    },
27    output::{
28        GetBucketAccelerateConfigurationOutput, GetBucketAclOutput, GetBucketCorsOutput,
29        GetBucketEncryptionOutput, GetBucketLifecycleConfigurationOutput, GetBucketLoggingOutput,
30        GetBucketNotificationConfigurationOutput, GetBucketOwnershipControlsOutput,
31        GetBucketPolicyOutput, GetBucketPolicyStatusOutput, GetBucketRequestPaymentOutput,
32        GetBucketTaggingOutput, GetBucketVersioningOutput, GetBucketWebsiteOutput,
33        GetObjectLockConfigurationOutput, GetPublicAccessBlockOutput,
34        PutBucketLifecycleConfigurationOutput, PutObjectLockConfigurationOutput,
35    },
36    types::{
37        BucketAccelerateStatus, BucketVersioningStatus, CORSRule,
38        DefaultRetention as ModelDefaultRetention, ErrorDocument, Grant, Grantee, IndexDocument,
39        ObjectLockConfiguration as ModelObjectLockConfiguration, ObjectLockEnabled,
40        ObjectLockRetentionMode, ObjectLockRule as ModelObjectLockRule, ObjectOwnership,
41        OwnershipControls, OwnershipControlsRule, Payer, Permission, PolicyStatus, Protocol,
42        PublicAccessBlockConfiguration, RedirectAllRequestsTo, ServerSideEncryption,
43        ServerSideEncryptionByDefault, ServerSideEncryptionConfiguration, ServerSideEncryptionRule,
44        Tag,
45    },
46};
47use tracing::debug;
48
49use super::bucket::to_model_owner;
50use crate::{
51    cors::CorsRule,
52    error::S3ServiceError,
53    provider::RustackS3,
54    state::{
55        bucket::{
56            BucketEncryption, CorsRuleConfig, ObjectLockConfiguration, ObjectLockRule,
57            OwnershipControlsConfig, PublicAccessBlockConfig, VersioningStatus, WebsiteConfig,
58        },
59        object::{CannedAcl, Owner as InternalOwner},
60    },
61};
62
63// These handler methods must remain async for consistency.
64#[allow(clippy::unused_async)]
65impl RustackS3 {
66    // -----------------------------------------------------------------------
67    // Versioning
68    // -----------------------------------------------------------------------
69
70    /// Get the versioning configuration for a bucket.
71    pub async fn handle_get_bucket_versioning(
72        &self,
73        input: GetBucketVersioningInput,
74    ) -> Result<GetBucketVersioningOutput, S3Error> {
75        let bucket_name = input.bucket;
76
77        let bucket = self
78            .state
79            .get_bucket(&bucket_name)
80            .map_err(S3ServiceError::into_s3_error)?;
81
82        let status = match *bucket.versioning.read() {
83            VersioningStatus::Enabled => Some(BucketVersioningStatus::from("Enabled")),
84            VersioningStatus::Suspended => Some(BucketVersioningStatus::from("Suspended")),
85            VersioningStatus::Disabled => None,
86        };
87
88        Ok(GetBucketVersioningOutput {
89            mfa_delete: None,
90            status,
91        })
92    }
93
94    /// Set the versioning configuration for a bucket.
95    pub async fn handle_put_bucket_versioning(
96        &self,
97        input: PutBucketVersioningInput,
98    ) -> Result<(), S3Error> {
99        let bucket_name = input.bucket;
100
101        let bucket = self
102            .state
103            .get_bucket(&bucket_name)
104            .map_err(S3ServiceError::into_s3_error)?;
105
106        let config = input.versioning_configuration;
107        if let Some(status) = config.status {
108            match status.as_str() {
109                "Enabled" => bucket.enable_versioning(),
110                "Suspended" => bucket.suspend_versioning(),
111                _ => {
112                    return Err(S3Error::invalid_argument("Invalid versioning status"));
113                }
114            }
115        }
116
117        debug!(bucket = %bucket_name, "put_bucket_versioning completed");
118        Ok(())
119    }
120
121    // -----------------------------------------------------------------------
122    // Encryption
123    // -----------------------------------------------------------------------
124
125    /// Get the server-side encryption configuration for a bucket.
126    pub async fn handle_get_bucket_encryption(
127        &self,
128        input: GetBucketEncryptionInput,
129    ) -> Result<GetBucketEncryptionOutput, S3Error> {
130        let bucket_name = input.bucket;
131
132        let bucket = self
133            .state
134            .get_bucket(&bucket_name)
135            .map_err(S3ServiceError::into_s3_error)?;
136
137        let enc = bucket.encryption.read();
138        let enc_config = enc
139            .as_ref()
140            .ok_or(S3ServiceError::ServerSideEncryptionConfigurationNotFoundError)
141            .map_err(S3ServiceError::into_s3_error)?;
142
143        let rule = ServerSideEncryptionRule {
144            apply_server_side_encryption_by_default: Some(ServerSideEncryptionByDefault {
145                kms_master_key_id: enc_config.kms_master_key_id.clone(),
146                sse_algorithm: ServerSideEncryption::from(enc_config.sse_algorithm.as_str()),
147            }),
148            blocked_encryption_types: None,
149            bucket_key_enabled: Some(enc_config.bucket_key_enabled),
150        };
151
152        Ok(GetBucketEncryptionOutput {
153            server_side_encryption_configuration: Some(ServerSideEncryptionConfiguration {
154                rules: vec![rule],
155            }),
156        })
157    }
158
159    /// Set the server-side encryption configuration for a bucket.
160    pub async fn handle_put_bucket_encryption(
161        &self,
162        input: PutBucketEncryptionInput,
163    ) -> Result<(), S3Error> {
164        let bucket_name = input.bucket;
165
166        let bucket = self
167            .state
168            .get_bucket(&bucket_name)
169            .map_err(S3ServiceError::into_s3_error)?;
170
171        let config = input.server_side_encryption_configuration;
172
173        let rule = config
174            .rules
175            .first()
176            .ok_or_else(|| S3Error::invalid_argument("At least one rule is required"))?;
177
178        let default = rule
179            .apply_server_side_encryption_by_default
180            .as_ref()
181            .ok_or_else(|| {
182                S3Error::invalid_argument("ApplyServerSideEncryptionByDefault is required")
183            })?;
184
185        let enc = BucketEncryption {
186            sse_algorithm: default.sse_algorithm.as_str().to_owned(),
187            kms_master_key_id: default.kms_master_key_id.clone(),
188            bucket_key_enabled: rule.bucket_key_enabled.unwrap_or(false),
189        };
190
191        *bucket.encryption.write() = Some(enc);
192
193        debug!(bucket = %bucket_name, "put_bucket_encryption completed");
194        Ok(())
195    }
196
197    /// Delete the server-side encryption configuration for a bucket.
198    pub async fn handle_delete_bucket_encryption(
199        &self,
200        input: DeleteBucketEncryptionInput,
201    ) -> Result<(), S3Error> {
202        let bucket_name = input.bucket;
203
204        let bucket = self
205            .state
206            .get_bucket(&bucket_name)
207            .map_err(S3ServiceError::into_s3_error)?;
208
209        *bucket.encryption.write() = None;
210
211        debug!(bucket = %bucket_name, "delete_bucket_encryption completed");
212        Ok(())
213    }
214
215    // -----------------------------------------------------------------------
216    // CORS
217    // -----------------------------------------------------------------------
218
219    /// Get CORS configuration for a bucket.
220    pub async fn handle_get_bucket_cors(
221        &self,
222        input: GetBucketCorsInput,
223    ) -> Result<GetBucketCorsOutput, S3Error> {
224        let bucket_name = input.bucket;
225
226        let bucket = self
227            .state
228            .get_bucket(&bucket_name)
229            .map_err(S3ServiceError::into_s3_error)?;
230
231        let cors_rules = bucket.cors_rules.read();
232        let rules = cors_rules
233            .as_ref()
234            .ok_or(S3ServiceError::NoSuchCorsConfiguration)
235            .map_err(S3ServiceError::into_s3_error)?;
236
237        let s3_rules: Vec<CORSRule> = rules.iter().map(cors_config_to_dto).collect();
238
239        Ok(GetBucketCorsOutput {
240            cors_rules: s3_rules,
241        })
242    }
243
244    /// Set CORS configuration for a bucket.
245    pub async fn handle_put_bucket_cors(&self, input: PutBucketCorsInput) -> Result<(), S3Error> {
246        let bucket_name = input.bucket;
247
248        let bucket = self
249            .state
250            .get_bucket(&bucket_name)
251            .map_err(S3ServiceError::into_s3_error)?;
252
253        let cors_config = input.cors_configuration;
254
255        let configs: Vec<CorsRuleConfig> = cors_config
256            .cors_rules
257            .iter()
258            .map(dto_to_cors_config)
259            .collect();
260        let index_rules: Vec<CorsRule> = configs
261            .iter()
262            .map(|c| CorsRule {
263                allowed_origins: c.allowed_origins.clone(),
264                allowed_methods: c.allowed_methods.clone(),
265                allowed_headers: c.allowed_headers.clone(),
266                expose_headers: c.expose_headers.clone(),
267                max_age_seconds: c.max_age_seconds,
268            })
269            .collect();
270
271        *bucket.cors_rules.write() = Some(configs);
272        self.cors_index.set_rules(&bucket_name, index_rules);
273
274        debug!(bucket = %bucket_name, "put_bucket_cors completed");
275        Ok(())
276    }
277
278    /// Delete CORS configuration for a bucket.
279    pub async fn handle_delete_bucket_cors(
280        &self,
281        input: DeleteBucketCorsInput,
282    ) -> Result<(), S3Error> {
283        let bucket_name = input.bucket;
284
285        let bucket = self
286            .state
287            .get_bucket(&bucket_name)
288            .map_err(S3ServiceError::into_s3_error)?;
289
290        *bucket.cors_rules.write() = None;
291        self.cors_index.delete_rules(&bucket_name);
292
293        debug!(bucket = %bucket_name, "delete_bucket_cors completed");
294        Ok(())
295    }
296
297    // -----------------------------------------------------------------------
298    // Lifecycle
299    // -----------------------------------------------------------------------
300
301    /// Get lifecycle configuration for a bucket.
302    pub async fn handle_get_bucket_lifecycle_configuration(
303        &self,
304        input: GetBucketLifecycleConfigurationInput,
305    ) -> Result<GetBucketLifecycleConfigurationOutput, S3Error> {
306        let bucket_name = input.bucket;
307
308        let bucket = self
309            .state
310            .get_bucket(&bucket_name)
311            .map_err(S3ServiceError::into_s3_error)?;
312
313        let lifecycle = bucket.lifecycle.read();
314        let config = lifecycle
315            .as_ref()
316            .ok_or(S3ServiceError::NoSuchLifecycleConfiguration)
317            .map_err(S3ServiceError::into_s3_error)?;
318
319        Ok(GetBucketLifecycleConfigurationOutput {
320            rules: config.rules.clone(),
321            transition_default_minimum_object_size: None,
322        })
323    }
324
325    /// Set lifecycle configuration for a bucket.
326    pub async fn handle_put_bucket_lifecycle_configuration(
327        &self,
328        input: PutBucketLifecycleConfigurationInput,
329    ) -> Result<PutBucketLifecycleConfigurationOutput, S3Error> {
330        let bucket_name = input.bucket;
331
332        let bucket = self
333            .state
334            .get_bucket(&bucket_name)
335            .map_err(S3ServiceError::into_s3_error)?;
336
337        *bucket.lifecycle.write() = input.lifecycle_configuration;
338
339        debug!(bucket = %bucket_name, "put_bucket_lifecycle_configuration completed");
340        Ok(PutBucketLifecycleConfigurationOutput {
341            transition_default_minimum_object_size: None,
342        })
343    }
344
345    /// Delete lifecycle configuration for a bucket.
346    pub async fn handle_delete_bucket_lifecycle(
347        &self,
348        input: DeleteBucketLifecycleInput,
349    ) -> Result<(), S3Error> {
350        let bucket_name = input.bucket;
351
352        let bucket = self
353            .state
354            .get_bucket(&bucket_name)
355            .map_err(S3ServiceError::into_s3_error)?;
356
357        *bucket.lifecycle.write() = None;
358
359        debug!(bucket = %bucket_name, "delete_bucket_lifecycle completed");
360        Ok(())
361    }
362
363    // -----------------------------------------------------------------------
364    // Policy
365    // -----------------------------------------------------------------------
366
367    /// Get the bucket policy.
368    pub async fn handle_get_bucket_policy(
369        &self,
370        input: GetBucketPolicyInput,
371    ) -> Result<GetBucketPolicyOutput, S3Error> {
372        let bucket_name = input.bucket;
373
374        let bucket = self
375            .state
376            .get_bucket(&bucket_name)
377            .map_err(S3ServiceError::into_s3_error)?;
378
379        let policy = bucket.policy.read();
380        let policy_str = policy
381            .as_ref()
382            .ok_or(S3ServiceError::NoSuchBucketPolicy)
383            .map_err(S3ServiceError::into_s3_error)?
384            .clone();
385
386        Ok(GetBucketPolicyOutput {
387            policy: Some(policy_str),
388        })
389    }
390
391    /// Set the bucket policy.
392    pub async fn handle_put_bucket_policy(
393        &self,
394        input: PutBucketPolicyInput,
395    ) -> Result<(), S3Error> {
396        let bucket_name = input.bucket;
397
398        let bucket = self
399            .state
400            .get_bucket(&bucket_name)
401            .map_err(S3ServiceError::into_s3_error)?;
402
403        *bucket.policy.write() = Some(input.policy);
404
405        debug!(bucket = %bucket_name, "put_bucket_policy completed");
406        Ok(())
407    }
408
409    /// Delete the bucket policy.
410    pub async fn handle_delete_bucket_policy(
411        &self,
412        input: DeleteBucketPolicyInput,
413    ) -> Result<(), S3Error> {
414        let bucket_name = input.bucket;
415
416        let bucket = self
417            .state
418            .get_bucket(&bucket_name)
419            .map_err(S3ServiceError::into_s3_error)?;
420
421        *bucket.policy.write() = None;
422
423        debug!(bucket = %bucket_name, "delete_bucket_policy completed");
424        Ok(())
425    }
426
427    // -----------------------------------------------------------------------
428    // Tagging
429    // -----------------------------------------------------------------------
430
431    /// Get the tag set for a bucket.
432    pub async fn handle_get_bucket_tagging(
433        &self,
434        input: GetBucketTaggingInput,
435    ) -> Result<GetBucketTaggingOutput, S3Error> {
436        let bucket_name = input.bucket;
437
438        let bucket = self
439            .state
440            .get_bucket(&bucket_name)
441            .map_err(S3ServiceError::into_s3_error)?;
442
443        let tags = bucket.tags.read();
444        if tags.is_empty() {
445            return Err(S3ServiceError::NoSuchTagSet.into_s3_error());
446        }
447
448        let tag_set: Vec<Tag> = tags
449            .iter()
450            .map(|(k, v)| Tag {
451                key: k.clone(),
452                value: v.clone(),
453            })
454            .collect();
455
456        Ok(GetBucketTaggingOutput { tag_set })
457    }
458
459    /// Set the tag set for a bucket.
460    pub async fn handle_put_bucket_tagging(
461        &self,
462        input: PutBucketTaggingInput,
463    ) -> Result<(), S3Error> {
464        let bucket_name = input.bucket;
465
466        let bucket = self
467            .state
468            .get_bucket(&bucket_name)
469            .map_err(S3ServiceError::into_s3_error)?;
470
471        let tagging = input.tagging;
472
473        let tags: Vec<(String, String)> = tagging
474            .tag_set
475            .into_iter()
476            .map(|t| (t.key, t.value))
477            .collect();
478
479        crate::validation::validate_tags(&tags).map_err(S3ServiceError::into_s3_error)?;
480
481        *bucket.tags.write() = tags;
482
483        debug!(bucket = %bucket_name, "put_bucket_tagging completed");
484        Ok(())
485    }
486
487    /// Delete the tag set for a bucket.
488    pub async fn handle_delete_bucket_tagging(
489        &self,
490        input: DeleteBucketTaggingInput,
491    ) -> Result<(), S3Error> {
492        let bucket_name = input.bucket;
493
494        let bucket = self
495            .state
496            .get_bucket(&bucket_name)
497            .map_err(S3ServiceError::into_s3_error)?;
498
499        *bucket.tags.write() = Vec::new();
500
501        debug!(bucket = %bucket_name, "delete_bucket_tagging completed");
502        Ok(())
503    }
504
505    // -----------------------------------------------------------------------
506    // Notification
507    // -----------------------------------------------------------------------
508
509    /// Get notification configuration for a bucket.
510    pub async fn handle_get_bucket_notification_configuration(
511        &self,
512        input: GetBucketNotificationConfigurationInput,
513    ) -> Result<GetBucketNotificationConfigurationOutput, S3Error> {
514        let bucket_name = input.bucket;
515
516        let bucket = self
517            .state
518            .get_bucket(&bucket_name)
519            .map_err(S3ServiceError::into_s3_error)?;
520
521        let notification_configuration = bucket.notification_configuration.read().clone();
522
523        Ok(GetBucketNotificationConfigurationOutput {
524            notification_configuration,
525        })
526    }
527
528    /// Set notification configuration for a bucket.
529    pub async fn handle_put_bucket_notification_configuration(
530        &self,
531        input: PutBucketNotificationConfigurationInput,
532    ) -> Result<(), S3Error> {
533        let bucket_name = input.bucket;
534
535        let bucket = self
536            .state
537            .get_bucket(&bucket_name)
538            .map_err(S3ServiceError::into_s3_error)?;
539
540        *bucket.notification_configuration.write() = Some(input.notification_configuration);
541
542        debug!(bucket = %bucket_name, "put_bucket_notification_configuration completed");
543        Ok(())
544    }
545
546    // -----------------------------------------------------------------------
547    // Logging
548    // -----------------------------------------------------------------------
549
550    /// Get logging configuration for a bucket.
551    pub async fn handle_get_bucket_logging(
552        &self,
553        input: GetBucketLoggingInput,
554    ) -> Result<GetBucketLoggingOutput, S3Error> {
555        let bucket_name = input.bucket;
556
557        let _bucket = self
558            .state
559            .get_bucket(&bucket_name)
560            .map_err(S3ServiceError::into_s3_error)?;
561
562        Ok(GetBucketLoggingOutput {
563            logging_enabled: None,
564        })
565    }
566
567    /// Set logging configuration for a bucket.
568    pub async fn handle_put_bucket_logging(
569        &self,
570        input: PutBucketLoggingInput,
571    ) -> Result<(), S3Error> {
572        let bucket_name = input.bucket;
573
574        let bucket = self
575            .state
576            .get_bucket(&bucket_name)
577            .map_err(S3ServiceError::into_s3_error)?;
578
579        *bucket.logging.write() = Some(serde_json::json!({"status": "configured"}));
580
581        debug!(bucket = %bucket_name, "put_bucket_logging completed");
582        Ok(())
583    }
584
585    // -----------------------------------------------------------------------
586    // Public Access Block
587    // -----------------------------------------------------------------------
588
589    /// Get public access block configuration for a bucket.
590    pub async fn handle_get_public_access_block(
591        &self,
592        input: GetPublicAccessBlockInput,
593    ) -> Result<GetPublicAccessBlockOutput, S3Error> {
594        let bucket_name = input.bucket;
595
596        let bucket = self
597            .state
598            .get_bucket(&bucket_name)
599            .map_err(S3ServiceError::into_s3_error)?;
600
601        let pab = bucket.public_access_block.read();
602        let config = pab
603            .as_ref()
604            .ok_or(S3ServiceError::NoSuchPublicAccessBlockConfiguration)
605            .map_err(S3ServiceError::into_s3_error)?;
606
607        Ok(GetPublicAccessBlockOutput {
608            public_access_block_configuration: Some(PublicAccessBlockConfiguration {
609                block_public_acls: Some(config.block_public_acls),
610                block_public_policy: Some(config.block_public_policy),
611                ignore_public_acls: Some(config.ignore_public_acls),
612                restrict_public_buckets: Some(config.restrict_public_buckets),
613            }),
614        })
615    }
616
617    /// Set public access block configuration for a bucket.
618    pub async fn handle_put_public_access_block(
619        &self,
620        input: PutPublicAccessBlockInput,
621    ) -> Result<(), S3Error> {
622        let bucket_name = input.bucket;
623
624        let bucket = self
625            .state
626            .get_bucket(&bucket_name)
627            .map_err(S3ServiceError::into_s3_error)?;
628
629        let config = input.public_access_block_configuration;
630
631        let internal_config = PublicAccessBlockConfig {
632            block_public_acls: config.block_public_acls.unwrap_or(false),
633            ignore_public_acls: config.ignore_public_acls.unwrap_or(false),
634            block_public_policy: config.block_public_policy.unwrap_or(false),
635            restrict_public_buckets: config.restrict_public_buckets.unwrap_or(false),
636        };
637
638        *bucket.public_access_block.write() = Some(internal_config);
639
640        debug!(bucket = %bucket_name, "put_public_access_block completed");
641        Ok(())
642    }
643
644    /// Delete public access block configuration for a bucket.
645    pub async fn handle_delete_public_access_block(
646        &self,
647        input: DeletePublicAccessBlockInput,
648    ) -> Result<(), S3Error> {
649        let bucket_name = input.bucket;
650
651        let bucket = self
652            .state
653            .get_bucket(&bucket_name)
654            .map_err(S3ServiceError::into_s3_error)?;
655
656        *bucket.public_access_block.write() = None;
657
658        debug!(bucket = %bucket_name, "delete_public_access_block completed");
659        Ok(())
660    }
661
662    // -----------------------------------------------------------------------
663    // Ownership Controls
664    // -----------------------------------------------------------------------
665
666    /// Get ownership controls configuration for a bucket.
667    pub async fn handle_get_bucket_ownership_controls(
668        &self,
669        input: GetBucketOwnershipControlsInput,
670    ) -> Result<GetBucketOwnershipControlsOutput, S3Error> {
671        let bucket_name = input.bucket;
672
673        let bucket = self
674            .state
675            .get_bucket(&bucket_name)
676            .map_err(S3ServiceError::into_s3_error)?;
677
678        let controls = bucket.ownership_controls.read();
679        let config = controls
680            .as_ref()
681            .ok_or(S3ServiceError::OwnershipControlsNotFoundError)
682            .map_err(S3ServiceError::into_s3_error)?;
683
684        let rule = OwnershipControlsRule {
685            object_ownership: ObjectOwnership::from(config.object_ownership.as_str()),
686        };
687
688        Ok(GetBucketOwnershipControlsOutput {
689            ownership_controls: Some(OwnershipControls { rules: vec![rule] }),
690        })
691    }
692
693    /// Set ownership controls configuration for a bucket.
694    pub async fn handle_put_bucket_ownership_controls(
695        &self,
696        input: PutBucketOwnershipControlsInput,
697    ) -> Result<(), S3Error> {
698        let bucket_name = input.bucket;
699
700        let bucket = self
701            .state
702            .get_bucket(&bucket_name)
703            .map_err(S3ServiceError::into_s3_error)?;
704
705        let controls = input.ownership_controls;
706
707        let rule = controls
708            .rules
709            .first()
710            .ok_or_else(|| S3Error::invalid_argument("At least one rule is required"))?;
711
712        let ownership = rule.object_ownership.as_str().to_owned();
713
714        *bucket.ownership_controls.write() = Some(OwnershipControlsConfig {
715            object_ownership: ownership,
716        });
717
718        debug!(bucket = %bucket_name, "put_bucket_ownership_controls completed");
719        Ok(())
720    }
721
722    /// Delete ownership controls configuration for a bucket.
723    pub async fn handle_delete_bucket_ownership_controls(
724        &self,
725        input: DeleteBucketOwnershipControlsInput,
726    ) -> Result<(), S3Error> {
727        let bucket_name = input.bucket;
728
729        let bucket = self
730            .state
731            .get_bucket(&bucket_name)
732            .map_err(S3ServiceError::into_s3_error)?;
733
734        *bucket.ownership_controls.write() = None;
735
736        debug!(bucket = %bucket_name, "delete_bucket_ownership_controls completed");
737        Ok(())
738    }
739
740    // -----------------------------------------------------------------------
741    // Object Lock Configuration
742    // -----------------------------------------------------------------------
743
744    /// Get object lock configuration for a bucket.
745    pub async fn handle_get_object_lock_configuration(
746        &self,
747        input: GetObjectLockConfigurationInput,
748    ) -> Result<GetObjectLockConfigurationOutput, S3Error> {
749        let bucket_name = input.bucket;
750
751        let bucket = self
752            .state
753            .get_bucket(&bucket_name)
754            .map_err(S3ServiceError::into_s3_error)?;
755
756        if !*bucket.object_lock_enabled.read() {
757            return Err(S3ServiceError::ObjectLockConfigurationNotFoundError.into_s3_error());
758        }
759
760        let lock_config = bucket.object_lock_configuration.read();
761        let rule = lock_config.as_ref().and_then(|c| {
762            c.rule.as_ref().map(|r| ModelObjectLockRule {
763                default_retention: r
764                    .default_retention
765                    .as_ref()
766                    .map(|dr| ModelDefaultRetention {
767                        days: dr.days,
768                        mode: Some(ObjectLockRetentionMode::from(dr.mode.as_str())),
769                        years: dr.years,
770                    }),
771            })
772        });
773
774        Ok(GetObjectLockConfigurationOutput {
775            object_lock_configuration: Some(ModelObjectLockConfiguration {
776                object_lock_enabled: Some(ObjectLockEnabled::from("Enabled")),
777                rule,
778            }),
779        })
780    }
781
782    /// Set object lock configuration for a bucket.
783    pub async fn handle_put_object_lock_configuration(
784        &self,
785        input: PutObjectLockConfigurationInput,
786    ) -> Result<PutObjectLockConfigurationOutput, S3Error> {
787        let bucket_name = input.bucket;
788
789        let bucket = self
790            .state
791            .get_bucket(&bucket_name)
792            .map_err(S3ServiceError::into_s3_error)?;
793
794        if let Some(config) = input.object_lock_configuration {
795            *bucket.object_lock_enabled.write() = true;
796            bucket.enable_versioning();
797
798            let internal_config = ObjectLockConfiguration {
799                object_lock_enabled: config
800                    .object_lock_enabled
801                    .as_ref()
802                    .map_or_else(|| "Enabled".to_owned(), |e| e.as_str().to_owned()),
803                rule: config.rule.map(|r| ObjectLockRule {
804                    default_retention: r.default_retention.map(|dr| {
805                        crate::state::bucket::DefaultRetention {
806                            mode: dr
807                                .mode
808                                .as_ref()
809                                .map(|m| m.as_str().to_owned())
810                                .unwrap_or_default(),
811                            days: dr.days,
812                            years: dr.years,
813                        }
814                    }),
815                }),
816            };
817            *bucket.object_lock_configuration.write() = Some(internal_config);
818        }
819
820        debug!(bucket = %bucket_name, "put_object_lock_configuration completed");
821        Ok(PutObjectLockConfigurationOutput {
822            request_charged: None,
823        })
824    }
825
826    // -----------------------------------------------------------------------
827    // Accelerate
828    // -----------------------------------------------------------------------
829
830    /// Get the transfer acceleration configuration for a bucket.
831    pub async fn handle_get_bucket_accelerate_configuration(
832        &self,
833        input: GetBucketAccelerateConfigurationInput,
834    ) -> Result<GetBucketAccelerateConfigurationOutput, S3Error> {
835        let bucket_name = input.bucket;
836
837        let bucket = self
838            .state
839            .get_bucket(&bucket_name)
840            .map_err(S3ServiceError::into_s3_error)?;
841
842        let status = bucket
843            .accelerate
844            .read()
845            .as_ref()
846            .map(|s| BucketAccelerateStatus::from(s.as_str()));
847
848        Ok(GetBucketAccelerateConfigurationOutput {
849            request_charged: None,
850            status,
851        })
852    }
853
854    /// Set the transfer acceleration configuration for a bucket.
855    pub async fn handle_put_bucket_accelerate_configuration(
856        &self,
857        input: PutBucketAccelerateConfigurationInput,
858    ) -> Result<(), S3Error> {
859        let bucket_name = input.bucket;
860
861        let bucket = self
862            .state
863            .get_bucket(&bucket_name)
864            .map_err(S3ServiceError::into_s3_error)?;
865
866        let config = input.accelerate_configuration;
867        let status = config.status.map(|s| s.as_str().to_owned());
868        *bucket.accelerate.write() = status;
869
870        debug!(bucket = %bucket_name, "put_bucket_accelerate_configuration completed");
871        Ok(())
872    }
873
874    // -----------------------------------------------------------------------
875    // Request Payment
876    // -----------------------------------------------------------------------
877
878    /// Get the request payment configuration for a bucket.
879    pub async fn handle_get_bucket_request_payment(
880        &self,
881        input: GetBucketRequestPaymentInput,
882    ) -> Result<GetBucketRequestPaymentOutput, S3Error> {
883        let bucket_name = input.bucket;
884
885        let bucket = self
886            .state
887            .get_bucket(&bucket_name)
888            .map_err(S3ServiceError::into_s3_error)?;
889
890        let payer = bucket.request_payment.read().clone();
891
892        Ok(GetBucketRequestPaymentOutput {
893            payer: Some(Payer::from(payer.as_str())),
894        })
895    }
896
897    /// Set the request payment configuration for a bucket.
898    pub async fn handle_put_bucket_request_payment(
899        &self,
900        input: PutBucketRequestPaymentInput,
901    ) -> Result<(), S3Error> {
902        let bucket_name = input.bucket;
903
904        let bucket = self
905            .state
906            .get_bucket(&bucket_name)
907            .map_err(S3ServiceError::into_s3_error)?;
908
909        let config = input.request_payment_configuration;
910        config
911            .payer
912            .as_str()
913            .clone_into(&mut bucket.request_payment.write());
914
915        debug!(bucket = %bucket_name, "put_bucket_request_payment completed");
916        Ok(())
917    }
918
919    // -----------------------------------------------------------------------
920    // Website
921    // -----------------------------------------------------------------------
922
923    /// Get the website configuration for a bucket.
924    pub async fn handle_get_bucket_website(
925        &self,
926        input: GetBucketWebsiteInput,
927    ) -> Result<GetBucketWebsiteOutput, S3Error> {
928        let bucket_name = input.bucket;
929
930        let bucket = self
931            .state
932            .get_bucket(&bucket_name)
933            .map_err(S3ServiceError::into_s3_error)?;
934
935        let guard = bucket.website.read();
936        let config = guard
937            .as_ref()
938            .ok_or_else(|| S3ServiceError::NoSuchWebsiteConfiguration.into_s3_error())?;
939
940        Ok(GetBucketWebsiteOutput {
941            index_document: config
942                .index_document_suffix
943                .as_ref()
944                .map(|s| IndexDocument { suffix: s.clone() }),
945            error_document: config
946                .error_document_key
947                .as_ref()
948                .map(|k| ErrorDocument { key: k.clone() }),
949            redirect_all_requests_to: config.redirect_all_requests_to_host.as_ref().map(|host| {
950                RedirectAllRequestsTo {
951                    host_name: host.clone(),
952                    protocol: config.redirect_all_requests_to_protocol.as_ref().map(|p| {
953                        match p.as_str() {
954                            "https" => Protocol::Https,
955                            _ => Protocol::Http,
956                        }
957                    }),
958                }
959            }),
960            routing_rules: Vec::new(),
961        })
962    }
963
964    /// Set the website configuration for a bucket.
965    pub async fn handle_put_bucket_website(
966        &self,
967        input: PutBucketWebsiteInput,
968    ) -> Result<(), S3Error> {
969        let bucket_name = input.bucket;
970
971        let bucket = self
972            .state
973            .get_bucket(&bucket_name)
974            .map_err(S3ServiceError::into_s3_error)?;
975
976        let wc = &input.website_configuration;
977        *bucket.website.write() = Some(WebsiteConfig {
978            index_document_suffix: wc.index_document.as_ref().map(|d| d.suffix.clone()),
979            error_document_key: wc.error_document.as_ref().map(|d| d.key.clone()),
980            redirect_all_requests_to_host: wc
981                .redirect_all_requests_to
982                .as_ref()
983                .map(|r| r.host_name.clone()),
984            redirect_all_requests_to_protocol: wc
985                .redirect_all_requests_to
986                .as_ref()
987                .and_then(|r| r.protocol.as_ref().map(|p| p.as_str().to_owned())),
988        });
989
990        debug!(bucket = %bucket_name, "put_bucket_website completed");
991        Ok(())
992    }
993
994    /// Delete the website configuration for a bucket.
995    pub async fn handle_delete_bucket_website(
996        &self,
997        input: DeleteBucketWebsiteInput,
998    ) -> Result<(), S3Error> {
999        let bucket_name = input.bucket;
1000
1001        let bucket = self
1002            .state
1003            .get_bucket(&bucket_name)
1004            .map_err(S3ServiceError::into_s3_error)?;
1005
1006        *bucket.website.write() = None;
1007
1008        debug!(bucket = %bucket_name, "delete_bucket_website completed");
1009        Ok(())
1010    }
1011
1012    // -----------------------------------------------------------------------
1013    // Bucket ACL
1014    // -----------------------------------------------------------------------
1015
1016    /// Get the ACL for a bucket.
1017    pub async fn handle_get_bucket_acl(
1018        &self,
1019        input: GetBucketAclInput,
1020    ) -> Result<GetBucketAclOutput, S3Error> {
1021        let bucket_name = input.bucket;
1022
1023        let bucket = self
1024            .state
1025            .get_bucket(&bucket_name)
1026            .map_err(S3ServiceError::into_s3_error)?;
1027
1028        let owner = to_model_owner(&bucket.owner);
1029        let acl = *bucket.acl.read();
1030        let grants = canned_acl_to_grants(&bucket.owner, acl);
1031
1032        Ok(GetBucketAclOutput {
1033            grants,
1034            owner: Some(owner),
1035        })
1036    }
1037
1038    /// Set the ACL for a bucket.
1039    pub async fn handle_put_bucket_acl(&self, input: PutBucketAclInput) -> Result<(), S3Error> {
1040        let bucket_name = input.bucket;
1041
1042        let bucket = self
1043            .state
1044            .get_bucket(&bucket_name)
1045            .map_err(S3ServiceError::into_s3_error)?;
1046
1047        if let Some(acl_val) = input.acl {
1048            let acl: CannedAcl = acl_val
1049                .as_str()
1050                .parse()
1051                .map_err(|_| S3Error::invalid_argument("Invalid canned ACL"))?;
1052            *bucket.acl.write() = acl;
1053        }
1054
1055        debug!(bucket = %bucket_name, "put_bucket_acl completed");
1056        Ok(())
1057    }
1058
1059    // -----------------------------------------------------------------------
1060    // Policy Status
1061    // -----------------------------------------------------------------------
1062
1063    /// Get the policy status for a bucket.
1064    pub async fn handle_get_bucket_policy_status(
1065        &self,
1066        input: GetBucketPolicyStatusInput,
1067    ) -> Result<GetBucketPolicyStatusOutput, S3Error> {
1068        let bucket_name = input.bucket;
1069
1070        let _bucket = self
1071            .state
1072            .get_bucket(&bucket_name)
1073            .map_err(S3ServiceError::into_s3_error)?;
1074
1075        Ok(GetBucketPolicyStatusOutput {
1076            policy_status: Some(PolicyStatus {
1077                is_public: Some(false),
1078            }),
1079        })
1080    }
1081}
1082
1083// ---------------------------------------------------------------------------
1084// Helpers
1085// ---------------------------------------------------------------------------
1086
1087/// Convert a [`CorsRuleConfig`] to a model [`CORSRule`].
1088fn cors_config_to_dto(config: &CorsRuleConfig) -> CORSRule {
1089    CORSRule {
1090        allowed_headers: config.allowed_headers.clone(),
1091        allowed_methods: config.allowed_methods.clone(),
1092        allowed_origins: config.allowed_origins.clone(),
1093        expose_headers: config.expose_headers.clone(),
1094        id: config.id.clone(),
1095        max_age_seconds: config.max_age_seconds,
1096    }
1097}
1098
1099/// Convert a model [`CORSRule`] to a [`CorsRuleConfig`].
1100fn dto_to_cors_config(rule: &CORSRule) -> CorsRuleConfig {
1101    CorsRuleConfig {
1102        id: rule.id.clone(),
1103        allowed_origins: rule.allowed_origins.clone(),
1104        allowed_methods: rule.allowed_methods.clone(),
1105        allowed_headers: rule.allowed_headers.clone(),
1106        expose_headers: rule.expose_headers.clone(),
1107        max_age_seconds: rule.max_age_seconds,
1108    }
1109}
1110
1111/// Convert a canned ACL to a list of model [`Grant`] DTOs.
1112fn canned_acl_to_grants(owner: &InternalOwner, acl: CannedAcl) -> Vec<Grant> {
1113    let owner_grant = Grant {
1114        grantee: Some(Grantee {
1115            display_name: Some(owner.display_name.clone()),
1116            email_address: None,
1117            id: Some(owner.id.clone()),
1118            r#type: rustack_s3_model::types::Type::from("CanonicalUser"),
1119            uri: None,
1120        }),
1121        permission: Some(Permission::from("FULL_CONTROL")),
1122    };
1123
1124    match acl {
1125        CannedAcl::PublicRead => {
1126            let public_read = Grant {
1127                grantee: Some(Grantee {
1128                    display_name: None,
1129                    email_address: None,
1130                    id: None,
1131                    r#type: rustack_s3_model::types::Type::from("Group"),
1132                    uri: Some("http://acs.amazonaws.com/groups/global/AllUsers".to_owned()),
1133                }),
1134                permission: Some(Permission::from("READ")),
1135            };
1136            vec![owner_grant, public_read]
1137        }
1138        _ => vec![owner_grant],
1139    }
1140}