Skip to main content

fakecloud_secretsmanager/
service.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use chrono::Utc;
5use http::StatusCode;
6use serde_json::{json, Value};
7
8use fakecloud_core::delivery::DeliveryBus;
9use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
10use fakecloud_core::validation::*;
11
12use crate::state::{RotationRules, Secret, SecretVersion, SharedSecretsManagerState};
13
14/// Information needed to invoke the rotation Lambda after releasing state lock.
15struct RotationInvocation {
16    lambda_arn: String,
17    secret_id: String,
18    client_request_token: String,
19}
20
21pub struct SecretsManagerService {
22    state: SharedSecretsManagerState,
23    delivery_bus: Option<Arc<DeliveryBus>>,
24}
25
26impl SecretsManagerService {
27    pub fn new(state: SharedSecretsManagerState) -> Self {
28        Self {
29            state,
30            delivery_bus: None,
31        }
32    }
33
34    pub fn with_delivery(mut self, delivery_bus: Arc<DeliveryBus>) -> Self {
35        self.delivery_bus = Some(delivery_bus);
36        self
37    }
38
39    fn create_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
40        let body = req.json_body();
41        validate_required("Name", &body["Name"])?;
42        let name = body["Name"]
43            .as_str()
44            .ok_or_else(|| {
45                AwsServiceError::aws_error(
46                    StatusCode::BAD_REQUEST,
47                    "InvalidParameterException",
48                    "Name is required",
49                )
50            })?
51            .to_string();
52        validate_string_length("name", &name, 1, 512)?;
53        validate_optional_string_length(
54            "clientRequestToken",
55            body["ClientRequestToken"].as_str(),
56            32,
57            64,
58        )?;
59        validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
60        validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
61        validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
62
63        let mut state = self.state.write();
64
65        let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
66        let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
67        let has_value = secret_string.is_some() || secret_binary.is_some();
68
69        let client_request_token = body["ClientRequestToken"].as_str().map(|s| s.to_string());
70
71        // Check for existing secret (idempotency)
72        if let Some(existing) = state.secrets.get(&name) {
73            if let Some(ref token) = client_request_token {
74                // Check if same token was used
75                if existing.versions.contains_key(token) {
76                    let version = &existing.versions[token];
77                    // Check if the value matches
78                    if version.secret_string == secret_string
79                        && version.secret_binary == secret_binary
80                    {
81                        // Idempotent: return same result
82                        let mut response = json!({
83                            "ARN": existing.arn,
84                            "Name": existing.name,
85                            "VersionId": token,
86                        });
87                        if !has_value {
88                            response.as_object_mut().unwrap().remove("VersionId");
89                        }
90                        return Ok(AwsResponse::ok_json(response));
91                    } else {
92                        return Err(AwsServiceError::aws_error(
93                            StatusCode::BAD_REQUEST,
94                            "ResourceExistsException",
95                            format!(
96                                "You can't use ClientRequestToken {token} because that value is already in use for a version of secret {}.",
97                                existing.arn
98                            ),
99                        ));
100                    }
101                }
102            }
103            return Err(AwsServiceError::aws_error(
104                StatusCode::BAD_REQUEST,
105                "ResourceExistsException",
106                format!("The operation failed because the secret {name} already exists."),
107            ));
108        }
109
110        let region = &req.region;
111        let account_id = &req.account_id;
112        let arn = format!(
113            "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
114            region,
115            account_id,
116            name,
117            &uuid::Uuid::new_v4().to_string()[..6]
118        );
119
120        let now = Utc::now();
121
122        let description = body["Description"].as_str().map(|s| s.to_string());
123        let kms_key_id = body["KmsKeyId"].as_str().map(|s| s.to_string());
124
125        let tags = parse_tags(&body["Tags"]);
126
127        let (versions, current_version_id, version_id_for_response) = if has_value {
128            let vid = client_request_token.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
129            let version = SecretVersion {
130                version_id: vid.clone(),
131                secret_string,
132                secret_binary,
133                stages: vec!["AWSCURRENT".to_string()],
134                created_at: now,
135            };
136            let mut versions = std::collections::HashMap::new();
137            versions.insert(vid.clone(), version);
138            (versions, Some(vid.clone()), Some(vid))
139        } else {
140            (std::collections::HashMap::new(), None, None)
141        };
142
143        let tags_ever_set = !tags.is_empty();
144        let secret = Secret {
145            name: name.clone(),
146            arn: arn.clone(),
147            description,
148            kms_key_id,
149            versions,
150            current_version_id,
151            tags,
152            tags_ever_set,
153            deleted: false,
154            deletion_date: None,
155            created_at: now,
156            last_changed_at: now,
157            last_accessed_at: None,
158            rotation_enabled: None,
159            rotation_lambda_arn: None,
160            rotation_rules: None,
161            last_rotated_at: None,
162            resource_policy: None,
163        };
164
165        state.secrets.insert(name.clone(), secret);
166
167        let mut response = json!({
168            "ARN": arn,
169            "Name": name,
170        });
171        if let Some(vid) = version_id_for_response {
172            response["VersionId"] = json!(vid);
173        }
174
175        Ok(AwsResponse::ok_json(response))
176    }
177
178    fn get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
179        let body = req.json_body();
180        let secret_id = require_secret_id(&body)?;
181        validate_optional_string_length("versionId", body["VersionId"].as_str(), 32, 64)?;
182        validate_optional_string_length("versionStage", body["VersionStage"].as_str(), 1, 256)?;
183
184        let mut state = self.state.write();
185        let secret = self.find_secret_mut(&mut state, &secret_id)?;
186
187        if secret.deleted {
188            return Err(AwsServiceError::aws_error(
189                StatusCode::BAD_REQUEST,
190                "InvalidRequestException",
191                "You can't perform this operation on the secret because it was marked for deletion.",
192            ));
193        }
194
195        let requested_stage = body["VersionStage"].as_str().unwrap_or("AWSCURRENT");
196
197        // Determine which version to return
198        let version_id = body["VersionId"]
199            .as_str()
200            .map(|s| s.to_string())
201            .or_else(|| {
202                secret
203                    .versions
204                    .iter()
205                    .find(|(_, v)| v.stages.contains(&requested_stage.to_string()))
206                    .map(|(id, _)| id.clone())
207            });
208
209        let version_id = match version_id {
210            Some(vid) => vid,
211            None => {
212                // No versions exist
213                return Err(AwsServiceError::aws_error(
214                    StatusCode::NOT_FOUND,
215                    "ResourceNotFoundException",
216                    format!(
217                        "Secrets Manager can't find the specified secret value for staging label: {requested_stage}"
218                    ),
219                ));
220            }
221        };
222
223        let version = secret.versions.get(&version_id).ok_or_else(|| {
224            AwsServiceError::aws_error(
225                StatusCode::NOT_FOUND,
226                "ResourceNotFoundException",
227                format!(
228                    "Secrets Manager can't find the specified secret value for VersionId: {version_id}"
229                ),
230            )
231        })?;
232
233        // If VersionStage is specified with VersionId, verify they match
234        if body["VersionId"].as_str().is_some() {
235            if let Some(stage) = body["VersionStage"].as_str() {
236                if !version.stages.contains(&stage.to_string()) {
237                    return Err(AwsServiceError::aws_error(
238                        StatusCode::NOT_FOUND,
239                        "ResourceNotFoundException",
240                        "You provided a VersionStage that is not associated to the provided VersionId.",
241                    ));
242                }
243            }
244        }
245
246        // Only set last_accessed_at on successful retrieval
247        secret.last_accessed_at = Some(Utc::now());
248
249        let mut response = json!({
250            "ARN": secret.arn,
251            "Name": secret.name,
252            "VersionId": version.version_id,
253            "VersionStages": version.stages,
254            "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
255        });
256
257        if let Some(ref s) = version.secret_string {
258            response["SecretString"] = json!(s);
259        }
260        if let Some(ref b) = version.secret_binary {
261            response["SecretBinary"] = json!(base64_encode(b));
262        }
263
264        Ok(AwsResponse::ok_json(response))
265    }
266
267    fn put_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
268        let body = req.json_body();
269        let secret_id = require_secret_id(&body)?;
270        validate_optional_string_length(
271            "clientRequestToken",
272            body["ClientRequestToken"].as_str(),
273            32,
274            64,
275        )?;
276        validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
277
278        let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
279        let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
280
281        // Validate that either SecretString or SecretBinary is provided
282        if secret_string.is_none() && secret_binary.is_none() {
283            return Err(AwsServiceError::aws_error(
284                StatusCode::BAD_REQUEST,
285                "InvalidRequestException",
286                "You must provide either SecretString or SecretBinary.",
287            ));
288        }
289
290        let mut state = self.state.write();
291        let secret = match self.find_secret_mut(&mut state, &secret_id) {
292            Ok(s) => s,
293            Err(_) => {
294                return Err(AwsServiceError::aws_error(
295                    StatusCode::NOT_FOUND,
296                    "ResourceNotFoundException",
297                    "Secrets Manager can't find the specified secret.",
298                ));
299            }
300        };
301
302        if secret.deleted {
303            return Err(AwsServiceError::aws_error(
304                StatusCode::BAD_REQUEST,
305                "InvalidRequestException",
306                "You can't perform this operation on the secret because it was marked for deletion.",
307            ));
308        }
309
310        let now = Utc::now();
311        let version_id = body["ClientRequestToken"]
312            .as_str()
313            .map(|s| s.to_string())
314            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
315
316        // Check for idempotent request (same token, same value)
317        if let Some(existing_version) = secret.versions.get(&version_id) {
318            if existing_version.secret_string == secret_string
319                && existing_version.secret_binary == secret_binary
320            {
321                // Idempotent: return existing version
322                let response = json!({
323                    "ARN": secret.arn,
324                    "Name": secret.name,
325                    "VersionId": version_id,
326                    "VersionStages": existing_version.stages,
327                });
328                return Ok(AwsResponse::ok_json(response));
329            } else {
330                return Err(AwsServiceError::aws_error(
331                    StatusCode::BAD_REQUEST,
332                    "ResourceExistsException",
333                    format!(
334                        "You can't use ClientRequestToken {version_id} because that value is already in use for a version of secret {}.",
335                        secret.arn
336                    ),
337                ));
338            }
339        }
340
341        let mut version_stages: Vec<String> = body["VersionStages"]
342            .as_array()
343            .map(|arr| {
344                arr.iter()
345                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
346                    .collect()
347            })
348            .unwrap_or_else(|| vec!["AWSCURRENT".to_string()]);
349
350        // If this is the first version with a value, add AWSCURRENT to stages
351        let has_current = secret
352            .versions
353            .values()
354            .any(|v| v.stages.contains(&"AWSCURRENT".to_string()));
355        if !has_current && !version_stages.contains(&"AWSCURRENT".to_string()) {
356            version_stages.push("AWSCURRENT".to_string());
357        }
358
359        // Move AWSCURRENT from old version to AWSPREVIOUS if new version has AWSCURRENT
360        if version_stages.contains(&"AWSCURRENT".to_string()) {
361            if let Some(ref old_vid) = secret.current_version_id.clone() {
362                if let Some(old_version) = secret.versions.get_mut(old_vid) {
363                    old_version.stages.retain(|s| s != "AWSCURRENT");
364                    if !old_version.stages.contains(&"AWSPREVIOUS".to_string()) {
365                        old_version.stages.push("AWSPREVIOUS".to_string());
366                    }
367                }
368                // Remove AWSPREVIOUS from any other version
369                for (id, v) in secret.versions.iter_mut() {
370                    if id != old_vid {
371                        v.stages.retain(|s| s != "AWSPREVIOUS");
372                    }
373                }
374            }
375            secret.current_version_id = Some(version_id.clone());
376        }
377
378        // Remove custom stages from other versions that have them
379        for stage in &version_stages {
380            if stage == "AWSCURRENT" || stage == "AWSPREVIOUS" {
381                continue;
382            }
383            for v in secret.versions.values_mut() {
384                v.stages.retain(|s| s != stage);
385            }
386        }
387
388        // Remove versions with no stages
389        secret.versions.retain(|_, v| !v.stages.is_empty());
390
391        let version = SecretVersion {
392            version_id: version_id.clone(),
393            secret_string,
394            secret_binary,
395            stages: version_stages.clone(),
396            created_at: now,
397        };
398
399        secret.versions.insert(version_id.clone(), version);
400        secret.last_changed_at = now;
401
402        let response = json!({
403            "ARN": secret.arn,
404            "Name": secret.name,
405            "VersionId": version_id,
406            "VersionStages": version_stages,
407        });
408
409        Ok(AwsResponse::ok_json(response))
410    }
411
412    fn update_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
413        let body = req.json_body();
414        let secret_id = require_secret_id(&body)?;
415        validate_optional_string_length(
416            "clientRequestToken",
417            body["ClientRequestToken"].as_str(),
418            32,
419            64,
420        )?;
421        validate_optional_string_length("description", body["Description"].as_str(), 0, 2048)?;
422        validate_optional_string_length("kmsKeyId", body["KmsKeyId"].as_str(), 0, 2048)?;
423        validate_optional_string_length("secretString", body["SecretString"].as_str(), 1, 65536)?;
424
425        let mut state = self.state.write();
426        let secret = match self.find_secret_mut(&mut state, &secret_id) {
427            Ok(s) => s,
428            Err(_) => {
429                return Err(AwsServiceError::aws_error(
430                    StatusCode::NOT_FOUND,
431                    "ResourceNotFoundException",
432                    "Secrets Manager can't find the specified secret.",
433                ));
434            }
435        };
436
437        if secret.deleted {
438            return Err(AwsServiceError::aws_error(
439                StatusCode::BAD_REQUEST,
440                "InvalidRequestException",
441                "You can't perform this operation on the secret because it was marked for deletion.",
442            ));
443        }
444
445        if let Some(desc) = body["Description"].as_str() {
446            secret.description = Some(desc.to_string());
447        }
448        if let Some(kms) = body["KmsKeyId"].as_str() {
449            secret.kms_key_id = Some(kms.to_string());
450        }
451
452        // If SecretString or SecretBinary is provided, create a new version
453        let secret_string = body["SecretString"].as_str().map(|s| s.to_string());
454        let secret_binary = body["SecretBinary"].as_str().and_then(base64_decode);
455
456        let version_id = if secret_string.is_some() || secret_binary.is_some() {
457            let vid = body["ClientRequestToken"]
458                .as_str()
459                .map(|s| s.to_string())
460                .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
461
462            // Check for idempotent request
463            if let Some(existing_version) = secret.versions.get(&vid) {
464                if existing_version.secret_string == secret_string
465                    && existing_version.secret_binary == secret_binary
466                {
467                    // Idempotent
468                    let response = json!({
469                        "ARN": secret.arn,
470                        "Name": secret.name,
471                        "VersionId": vid,
472                    });
473                    return Ok(AwsResponse::ok_json(response));
474                } else {
475                    return Err(AwsServiceError::aws_error(
476                        StatusCode::BAD_REQUEST,
477                        "ResourceExistsException",
478                        format!(
479                            "You can't use ClientRequestToken {vid} because that value is already in use for a version of secret {}.",
480                            secret.arn
481                        ),
482                    ));
483                }
484            }
485
486            let now = Utc::now();
487
488            // Move AWSCURRENT -> AWSPREVIOUS on old version
489            if let Some(ref old_vid) = secret.current_version_id.clone() {
490                if let Some(old_v) = secret.versions.get_mut(old_vid) {
491                    old_v.stages.retain(|s| s != "AWSCURRENT");
492                    if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
493                        old_v.stages.push("AWSPREVIOUS".to_string());
494                    }
495                }
496            }
497
498            let version = SecretVersion {
499                version_id: vid.clone(),
500                secret_string,
501                secret_binary,
502                stages: vec!["AWSCURRENT".to_string()],
503                created_at: now,
504            };
505            secret.versions.insert(vid.clone(), version);
506            secret.current_version_id = Some(vid.clone());
507            secret.last_changed_at = now;
508            Some(vid)
509        } else {
510            secret.last_changed_at = Utc::now();
511            None
512        };
513
514        let mut response = json!({
515            "ARN": secret.arn,
516            "Name": secret.name,
517        });
518        if let Some(vid) = version_id {
519            response["VersionId"] = json!(vid);
520        }
521
522        Ok(AwsResponse::ok_json(response))
523    }
524
525    fn delete_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
526        let body = req.json_body();
527        let secret_id = require_secret_id(&body)?;
528
529        let force_delete = body["ForceDeleteWithoutRecovery"]
530            .as_bool()
531            .unwrap_or(false);
532        let recovery_window = body.get("RecoveryWindowInDays").and_then(|v| v.as_i64());
533
534        // Validate recovery window range first (AWS validates this before the conflict check)
535        if let Some(days) = recovery_window {
536            if !(7..=30).contains(&days) {
537                return Err(AwsServiceError::aws_error(
538                    StatusCode::BAD_REQUEST,
539                    "InvalidParameterException",
540                    "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: RecoveryWindowInDays value must be between 7 and 30 days (inclusive).",
541                ));
542            }
543        }
544
545        // Validate: can't use both force delete and recovery window
546        if force_delete && recovery_window.is_some() {
547            return Err(AwsServiceError::aws_error(
548                StatusCode::BAD_REQUEST,
549                "InvalidParameterException",
550                "An error occurred (InvalidParameterException) when calling the DeleteSecret operation: You can't use ForceDeleteWithoutRecovery in conjunction with RecoveryWindowInDays.",
551            ));
552        }
553
554        let mut state = self.state.write();
555
556        if force_delete {
557            // Force delete: if secret doesn't exist, create a fake response
558            match self.find_secret_mut(&mut state, &secret_id) {
559                Ok(secret) => {
560                    let arn = secret.arn.clone();
561                    let name = secret.name.clone();
562                    let deletion_date = Utc::now();
563                    state.secrets.remove(&name);
564                    let response = json!({
565                        "ARN": arn,
566                        "Name": name,
567                        "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
568                    });
569                    return Ok(AwsResponse::ok_json(response));
570                }
571                Err(_) => {
572                    // For force delete of non-existent secret, AWS returns success
573                    let arn = format!(
574                        "arn:aws:secretsmanager:{}:{}:secret:{}-{}",
575                        req.region,
576                        req.account_id,
577                        secret_id,
578                        &uuid::Uuid::new_v4().to_string()[..6]
579                    );
580                    let deletion_date = Utc::now();
581                    let response = json!({
582                        "ARN": arn,
583                        "Name": secret_id,
584                        "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
585                    });
586                    return Ok(AwsResponse::ok_json(response));
587                }
588            }
589        }
590
591        let secret = self.find_secret_mut(&mut state, &secret_id)?;
592
593        if secret.deleted {
594            return Err(AwsServiceError::aws_error(
595                StatusCode::BAD_REQUEST,
596                "InvalidRequestException",
597                "You can't perform this operation on the secret because it was already scheduled for deletion.",
598            ));
599        }
600
601        let now = Utc::now();
602        let days = recovery_window.unwrap_or(30);
603        let deletion_date = now + chrono::Duration::days(days);
604        secret.deleted = true;
605        secret.deletion_date = Some(deletion_date);
606
607        let response = json!({
608            "ARN": secret.arn,
609            "Name": secret.name,
610            "DeletionDate": deletion_date.timestamp_millis() as f64 / 1000.0,
611        });
612
613        Ok(AwsResponse::ok_json(response))
614    }
615
616    fn restore_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
617        let body = req.json_body();
618        let secret_id = require_secret_id(&body)?;
619
620        let mut state = self.state.write();
621        let secret = self.find_secret_mut(&mut state, &secret_id)?;
622
623        // AWS allows restoring a secret that is not deleted (no-op)
624        secret.deleted = false;
625        secret.deletion_date = None;
626
627        let response = json!({
628            "ARN": secret.arn,
629            "Name": secret.name,
630        });
631
632        Ok(AwsResponse::ok_json(response))
633    }
634
635    fn describe_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
636        let body = req.json_body();
637        let secret_id = require_secret_id(&body)?;
638
639        let state = self.state.read();
640        let secret = self.find_secret_ref(&state, &secret_id)?;
641
642        let mut response = json!({
643            "ARN": secret.arn,
644            "Name": secret.name,
645            "CreatedDate": secret.created_at.timestamp_millis() as f64 / 1000.0,
646            "LastChangedDate": secret.last_changed_at.timestamp_millis() as f64 / 1000.0,
647        });
648
649        if !secret.versions.is_empty() {
650            let mut version_ids_to_stages: serde_json::Map<String, Value> = serde_json::Map::new();
651            for (vid, version) in &secret.versions {
652                version_ids_to_stages.insert(vid.clone(), json!(version.stages));
653            }
654            response["VersionIdsToStages"] = Value::Object(version_ids_to_stages);
655        }
656
657        if let Some(ref desc) = secret.description {
658            if !desc.is_empty() {
659                response["Description"] = json!(desc);
660            }
661        }
662
663        if secret.tags_ever_set || !secret.tags.is_empty() {
664            response["Tags"] = json!(tags_to_json(&secret.tags));
665        }
666
667        if let Some(ref kms) = secret.kms_key_id {
668            response["KmsKeyId"] = json!(kms);
669        }
670        if secret.deleted {
671            response["DeletedDate"] = json!(secret
672                .deletion_date
673                .map(|d| d.timestamp_millis() as f64 / 1000.0));
674        }
675        if let Some(rotation_enabled) = secret.rotation_enabled {
676            response["RotationEnabled"] = json!(rotation_enabled);
677        }
678        if let Some(ref lambda_arn) = secret.rotation_lambda_arn {
679            response["RotationLambdaARN"] = json!(lambda_arn);
680        }
681        if let Some(ref rules) = secret.rotation_rules {
682            let mut rules_json = json!({});
683            if let Some(days) = rules.automatically_after_days {
684                rules_json["AutomaticallyAfterDays"] = json!(days);
685            }
686            response["RotationRules"] = rules_json;
687        }
688        if let Some(last_rotated) = secret.last_rotated_at {
689            response["LastRotatedDate"] = json!(last_rotated.timestamp_millis() as f64 / 1000.0);
690        }
691        // Calculate NextRotationDate if rotation is enabled
692        if secret.rotation_enabled == Some(true) {
693            if let Some(ref rules) = secret.rotation_rules {
694                if let Some(days) = rules.automatically_after_days {
695                    let base = secret.last_rotated_at.unwrap_or(secret.created_at);
696                    let next = base + chrono::Duration::days(days);
697                    response["NextRotationDate"] = json!(next.timestamp_millis() as f64 / 1000.0);
698                }
699            }
700        }
701
702        Ok(AwsResponse::ok_json(response))
703    }
704
705    fn list_secrets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
706        let body = req.json_body();
707        validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
708        validate_optional_range_i64("maxResults", body["MaxResults"].as_i64(), 1, 100)?;
709        validate_optional_enum("sortBy", body["SortBy"].as_str(), &["name", "created-date"])?;
710        validate_optional_enum("sortOrder", body["SortOrder"].as_str(), &["asc", "desc"])?;
711        let max_results = body["MaxResults"].as_i64().unwrap_or(100) as usize;
712        let next_token = body["NextToken"].as_str();
713        let filters = body["Filters"].as_array();
714        let include_deleted = body["IncludePlannedDeletion"].as_bool().unwrap_or(false);
715
716        // Validate filters
717        if let Some(filters) = filters {
718            for filter in filters {
719                let key = filter["Key"].as_str().unwrap_or("");
720                let values = filter["Values"].as_array();
721
722                if key.is_empty() {
723                    return Err(AwsServiceError::aws_error(
724                        StatusCode::BAD_REQUEST,
725                        "InvalidParameterException",
726                        "Invalid filter key",
727                    ));
728                }
729
730                let valid_keys = [
731                    "all",
732                    "name",
733                    "tag-key",
734                    "description",
735                    "tag-value",
736                    "owning-service",
737                    "primary-region",
738                ];
739                if !valid_keys.contains(&key) {
740                    return Err(AwsServiceError::aws_error(
741                        StatusCode::BAD_REQUEST,
742                        "ValidationException",
743                        format!(
744                            "1 validation error detected: Value '{}' at 'filters.1.member.key' failed to satisfy constraint: Member must satisfy enum value set: [all, name, tag-key, description, tag-value]",
745                            key
746                        ),
747                    ));
748                }
749
750                if values.is_none() || values.unwrap().is_empty() {
751                    return Err(AwsServiceError::aws_error(
752                        StatusCode::BAD_REQUEST,
753                        "InvalidParameterException",
754                        format!("Invalid filter values for key: {key}"),
755                    ));
756                }
757            }
758        }
759
760        let state = self.state.read();
761
762        let mut secrets: Vec<&Secret> = state
763            .secrets
764            .values()
765            .filter(|s| {
766                // Exclude deleted unless IncludePlannedDeletion
767                if s.deleted && !include_deleted {
768                    return false;
769                }
770
771                if let Some(filters) = filters {
772                    for filter in filters {
773                        let key = filter["Key"].as_str().unwrap_or("");
774                        let values: Vec<&str> = filter["Values"]
775                            .as_array()
776                            .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
777                            .unwrap_or_default();
778
779                        let matches = match key {
780                            "name" => filter_name(s, &values),
781                            "description" => filter_description(s, &values),
782                            "tag-key" => filter_tag_key(s, &values),
783                            "tag-value" => filter_tag_value(s, &values),
784                            "all" => filter_all(s, &values),
785                            "owning-service" => false,
786                            "primary-region" => false,
787                            _ => true,
788                        };
789
790                        if !matches {
791                            return false;
792                        }
793                    }
794                }
795                true
796            })
797            .collect();
798        secrets.sort_by(|a, b| a.created_at.cmp(&b.created_at));
799
800        // Simple pagination with name-based token
801        let start_idx = if let Some(token) = next_token {
802            secrets.iter().position(|s| s.name == token).unwrap_or(0)
803        } else {
804            0
805        };
806
807        let page: Vec<Value> = secrets
808            .iter()
809            .skip(start_idx)
810            .take(max_results)
811            .map(|s| {
812                let mut entry = json!({
813                    "ARN": s.arn,
814                    "Name": s.name,
815                    "CreatedDate": s.created_at.timestamp_millis() as f64 / 1000.0,
816                    "LastChangedDate": s.last_changed_at.timestamp_millis() as f64 / 1000.0,
817                });
818
819                if !s.versions.is_empty() {
820                    let mut version_ids_to_stages: serde_json::Map<String, Value> =
821                        serde_json::Map::new();
822                    for (vid, version) in &s.versions {
823                        version_ids_to_stages.insert(vid.clone(), json!(version.stages));
824                    }
825                    entry["SecretVersionsToStages"] = Value::Object(version_ids_to_stages);
826                }
827
828                if let Some(ref desc) = s.description {
829                    if !desc.is_empty() {
830                        entry["Description"] = json!(desc);
831                    }
832                }
833
834                if s.tags_ever_set || !s.tags.is_empty() {
835                    entry["Tags"] = json!(tags_to_json(&s.tags));
836                }
837
838                if let Some(ref kms) = s.kms_key_id {
839                    entry["KmsKeyId"] = json!(kms);
840                }
841                if s.deleted {
842                    entry["DeletedDate"] = json!(s
843                        .deletion_date
844                        .map(|d| d.timestamp_millis() as f64 / 1000.0));
845                }
846                entry
847            })
848            .collect();
849
850        let has_more = start_idx + max_results < secrets.len();
851        let mut response = json!({
852            "SecretList": page,
853        });
854        if has_more {
855            if let Some(next) = secrets.get(start_idx + max_results) {
856                response["NextToken"] = json!(next.name);
857            }
858        }
859
860        Ok(AwsResponse::ok_json(response))
861    }
862
863    fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
864        let body = req.json_body();
865        let secret_id = require_secret_id(&body)?;
866
867        let new_tags = parse_tags(&body["Tags"]);
868
869        let mut state = self.state.write();
870        let secret = self.find_secret_mut(&mut state, &secret_id)?;
871
872        if !new_tags.is_empty() {
873            secret.tags_ever_set = true;
874        }
875        for (k, v) in new_tags {
876            // Update existing tag or add new one
877            if let Some(existing) = secret.tags.iter_mut().find(|(ek, _)| *ek == k) {
878                existing.1 = v;
879            } else {
880                secret.tags.push((k, v));
881            }
882        }
883
884        Ok(AwsResponse::json(StatusCode::OK, "{}"))
885    }
886
887    fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
888        let body = req.json_body();
889        let secret_id = require_secret_id(&body)?;
890
891        let tag_keys: Vec<String> = body["TagKeys"]
892            .as_array()
893            .map(|arr| {
894                arr.iter()
895                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
896                    .collect()
897            })
898            .unwrap_or_default();
899
900        let mut state = self.state.write();
901        let secret = self.find_secret_mut(&mut state, &secret_id)?;
902
903        secret.tags.retain(|(k, _)| !tag_keys.contains(k));
904
905        Ok(AwsResponse::json(StatusCode::OK, "{}"))
906    }
907
908    fn list_secret_version_ids(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
909        let body = req.json_body();
910        let secret_id = require_secret_id(&body)?;
911        validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
912
913        let state = self.state.read();
914        let secret = self.find_secret_ref(&state, &secret_id)?;
915
916        let versions: Vec<Value> = secret
917            .versions
918            .values()
919            .map(|v| {
920                json!({
921                    "VersionId": v.version_id,
922                    "VersionStages": v.stages,
923                    "CreatedDate": v.created_at.timestamp_millis() as f64 / 1000.0,
924                })
925            })
926            .collect();
927
928        let response = json!({
929            "ARN": secret.arn,
930            "Name": secret.name,
931            "Versions": versions,
932        });
933
934        Ok(AwsResponse::ok_json(response))
935    }
936
937    fn get_random_password(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
938        let body = req.json_body();
939        let length = body["PasswordLength"].as_i64().unwrap_or(32) as usize;
940
941        if length < 4 {
942            return Err(AwsServiceError::aws_error(
943                StatusCode::BAD_REQUEST,
944                "InvalidParameterException",
945                "InvalidParameterException",
946            ));
947        }
948        if length > 4096 {
949            return Err(AwsServiceError::aws_error(
950                StatusCode::BAD_REQUEST,
951                "InvalidParameterValue",
952                "InvalidParameterValue",
953            ));
954        }
955
956        let exclude_lowercase = body["ExcludeLowercase"].as_bool().unwrap_or(false);
957        let exclude_uppercase = body["ExcludeUppercase"].as_bool().unwrap_or(false);
958        let exclude_numbers = body["ExcludeNumbers"].as_bool().unwrap_or(false);
959        let exclude_punctuation = body["ExcludePunctuation"].as_bool().unwrap_or(false);
960        let include_space = body["IncludeSpace"].as_bool().unwrap_or(false);
961        let require_each = body["RequireEachIncludedType"].as_bool().unwrap_or(true);
962        validate_optional_string_length(
963            "excludeCharacters",
964            body["ExcludeCharacters"].as_str(),
965            0,
966            4096,
967        )?;
968        let exclude_chars = body["ExcludeCharacters"].as_str().unwrap_or("").to_string();
969
970        let lowercase = "abcdefghijklmnopqrstuvwxyz";
971        let uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
972        let digits = "0123456789";
973        let punctuation = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~";
974
975        let mut char_pool = String::new();
976        let mut required_chars: Vec<String> = Vec::new();
977
978        if !exclude_lowercase {
979            let filtered: String = lowercase
980                .chars()
981                .filter(|c| !exclude_chars.contains(*c))
982                .collect();
983            if !filtered.is_empty() {
984                required_chars.push(filtered.clone());
985                char_pool.push_str(&filtered);
986            }
987        }
988        if !exclude_uppercase {
989            let filtered: String = uppercase
990                .chars()
991                .filter(|c| !exclude_chars.contains(*c))
992                .collect();
993            if !filtered.is_empty() {
994                required_chars.push(filtered.clone());
995                char_pool.push_str(&filtered);
996            }
997        }
998        if !exclude_numbers {
999            let filtered: String = digits
1000                .chars()
1001                .filter(|c| !exclude_chars.contains(*c))
1002                .collect();
1003            if !filtered.is_empty() {
1004                required_chars.push(filtered.clone());
1005                char_pool.push_str(&filtered);
1006            }
1007        }
1008        if !exclude_punctuation {
1009            let filtered: String = punctuation
1010                .chars()
1011                .filter(|c| !exclude_chars.contains(*c))
1012                .collect();
1013            if !filtered.is_empty() {
1014                required_chars.push(filtered.clone());
1015                char_pool.push_str(&filtered);
1016            }
1017        }
1018        if include_space && !exclude_chars.contains(' ') {
1019            char_pool.push(' ');
1020        }
1021
1022        if char_pool.is_empty() {
1023            return Err(AwsServiceError::aws_error(
1024                StatusCode::BAD_REQUEST,
1025                "InvalidParameterException",
1026                "InvalidParameterException",
1027            ));
1028        }
1029
1030        let pool_bytes: Vec<char> = char_pool.chars().collect();
1031        let mut password = String::with_capacity(length);
1032
1033        // Use simple random generation
1034        if require_each {
1035            // First, ensure at least one character from each required category
1036            for category in &required_chars {
1037                let chars: Vec<char> = category.chars().collect();
1038                let idx = simple_random() % chars.len();
1039                password.push(chars[idx]);
1040            }
1041            if include_space && !exclude_chars.contains(' ') {
1042                password.push(' ');
1043            }
1044        }
1045
1046        // Fill the rest randomly
1047        while password.len() < length {
1048            let idx = simple_random() % pool_bytes.len();
1049            password.push(pool_bytes[idx]);
1050        }
1051
1052        // Shuffle the password (Fisher-Yates)
1053        let mut chars: Vec<char> = password.chars().collect();
1054        for i in (1..chars.len()).rev() {
1055            let j = simple_random() % (i + 1);
1056            chars.swap(i, j);
1057        }
1058        let password: String = chars.into_iter().take(length).collect();
1059
1060        let response = json!({
1061            "RandomPassword": password,
1062        });
1063
1064        Ok(AwsResponse::ok_json(response))
1065    }
1066
1067    fn rotate_secret(
1068        &self,
1069        req: &AwsRequest,
1070    ) -> Result<(AwsResponse, Option<RotationInvocation>), AwsServiceError> {
1071        let body = req.json_body();
1072        let secret_id = require_secret_id(&body)?;
1073
1074        // Validate ClientRequestToken
1075        if let Some(token) = body["ClientRequestToken"].as_str() {
1076            if token.len() < 32 || token.len() > 64 {
1077                return Err(AwsServiceError::aws_error(
1078                    StatusCode::BAD_REQUEST,
1079                    "InvalidParameterException",
1080                    "ClientRequestToken must be 32-64 characters long.",
1081                ));
1082            }
1083        }
1084
1085        // Validate RotationLambdaARN
1086        if let Some(arn) = body["RotationLambdaARN"].as_str() {
1087            if arn.len() > 2048 {
1088                return Err(AwsServiceError::aws_error(
1089                    StatusCode::BAD_REQUEST,
1090                    "InvalidParameterException",
1091                    "RotationLambdaARN length must be less than or equal to 2048.",
1092                ));
1093            }
1094        }
1095
1096        // Validate RotationRules
1097        if let Some(rules) = body["RotationRules"].as_object() {
1098            if let Some(days) = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64()) {
1099                if !(1..=1000).contains(&days) {
1100                    return Err(AwsServiceError::aws_error(
1101                        StatusCode::BAD_REQUEST,
1102                        "InvalidParameterException",
1103                        "RotationRules.AutomaticallyAfterDays must be within 1-1000.",
1104                    ));
1105                }
1106            }
1107        }
1108
1109        let mut state = self.state.write();
1110        let secret = self.find_secret_mut(&mut state, &secret_id)?;
1111
1112        if secret.deleted {
1113            return Err(AwsServiceError::aws_error(
1114                StatusCode::BAD_REQUEST,
1115                "InvalidRequestException",
1116                "You can't perform this operation on the secret because it was marked for deletion.",
1117            ));
1118        }
1119
1120        // Set rotation config
1121        if let Some(lambda_arn) = body["RotationLambdaARN"].as_str() {
1122            secret.rotation_lambda_arn = Some(lambda_arn.to_string());
1123        }
1124
1125        if let Some(rules) = body["RotationRules"].as_object() {
1126            let days = rules.get("AutomaticallyAfterDays").and_then(|v| v.as_i64());
1127            secret.rotation_rules = Some(RotationRules {
1128                automatically_after_days: days,
1129            });
1130        }
1131
1132        secret.rotation_enabled = Some(true);
1133        let now = Utc::now();
1134        secret.last_rotated_at = Some(now);
1135        secret.last_changed_at = now;
1136
1137        let version_id = body["ClientRequestToken"]
1138            .as_str()
1139            .map(|s| s.to_string())
1140            .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
1141
1142        let has_lambda =
1143            body["RotationLambdaARN"].as_str().is_some() || secret.rotation_lambda_arn.is_some();
1144        let lambda_arn = secret.rotation_lambda_arn.clone();
1145
1146        // If the secret has a value, perform rotation
1147        let mut invocation = None;
1148        if let Some(current_vid) = secret.current_version_id.clone() {
1149            let current_value = secret.versions.get(&current_vid).cloned();
1150
1151            if let Some(cv) = current_value {
1152                if has_lambda {
1153                    // With Lambda: create AWSPENDING version for Lambda to process
1154                    let version = SecretVersion {
1155                        version_id: version_id.clone(),
1156                        secret_string: cv.secret_string.clone(),
1157                        secret_binary: cv.secret_binary.clone(),
1158                        stages: vec!["AWSPENDING".to_string()],
1159                        created_at: now,
1160                    };
1161                    secret.versions.insert(version_id.clone(), version);
1162
1163                    // Schedule Lambda invocation
1164                    if let Some(ref arn) = lambda_arn {
1165                        invocation = Some(RotationInvocation {
1166                            lambda_arn: arn.clone(),
1167                            secret_id: secret.arn.clone(),
1168                            client_request_token: version_id.clone(),
1169                        });
1170                    }
1171                } else {
1172                    // Without Lambda: simple rotation - new version becomes AWSCURRENT
1173                    // Move old version to AWSPREVIOUS
1174                    if let Some(old_v) = secret.versions.get_mut(&current_vid) {
1175                        old_v.stages.retain(|s| s != "AWSCURRENT");
1176                        if !old_v.stages.contains(&"AWSPREVIOUS".to_string()) {
1177                            old_v.stages.push("AWSPREVIOUS".to_string());
1178                        }
1179                    }
1180                    let version = SecretVersion {
1181                        version_id: version_id.clone(),
1182                        secret_string: cv.secret_string.clone(),
1183                        secret_binary: cv.secret_binary.clone(),
1184                        stages: vec!["AWSCURRENT".to_string()],
1185                        created_at: now,
1186                    };
1187                    secret.versions.insert(version_id.clone(), version);
1188                    secret.current_version_id = Some(version_id.clone());
1189                }
1190            }
1191        }
1192
1193        let response = json!({
1194            "ARN": secret.arn,
1195            "Name": secret.name,
1196            "VersionId": version_id,
1197        });
1198
1199        Ok((AwsResponse::ok_json(response), invocation))
1200    }
1201
1202    fn cancel_rotate_secret(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1203        let body = req.json_body();
1204        let secret_id = require_secret_id(&body)?;
1205
1206        let mut state = self.state.write();
1207        let secret = self.find_secret_mut(&mut state, &secret_id)?;
1208
1209        if secret.deleted {
1210            return Err(AwsServiceError::aws_error(
1211                StatusCode::BAD_REQUEST,
1212                "InvalidRequestException",
1213                "You can't perform this operation on the secret because it was marked for deletion.",
1214            ));
1215        }
1216
1217        if secret.rotation_enabled != Some(true) {
1218            return Err(AwsServiceError::aws_error(
1219                StatusCode::BAD_REQUEST,
1220                "InvalidRequestException",
1221                "You can't cancel rotation for a secret that does not have rotation enabled.",
1222            ));
1223        }
1224
1225        secret.rotation_enabled = Some(false);
1226
1227        let response = json!({
1228            "ARN": secret.arn,
1229            "Name": secret.name,
1230        });
1231
1232        Ok(AwsResponse::ok_json(response))
1233    }
1234
1235    fn update_secret_version_stage(
1236        &self,
1237        req: &AwsRequest,
1238    ) -> Result<AwsResponse, AwsServiceError> {
1239        let body = req.json_body();
1240        let secret_id = require_secret_id(&body)?;
1241        let version_stage = body["VersionStage"]
1242            .as_str()
1243            .ok_or_else(|| {
1244                AwsServiceError::aws_error(
1245                    StatusCode::BAD_REQUEST,
1246                    "InvalidParameterException",
1247                    "VersionStage is required",
1248                )
1249            })?
1250            .to_string();
1251        validate_string_length("versionStage", &version_stage, 1, 256)?;
1252        validate_optional_string_length(
1253            "removeFromVersionId",
1254            body["RemoveFromVersionId"].as_str(),
1255            32,
1256            64,
1257        )?;
1258        validate_optional_string_length(
1259            "moveToVersionId",
1260            body["MoveToVersionId"].as_str(),
1261            32,
1262            64,
1263        )?;
1264
1265        let move_to = body["MoveToVersionId"].as_str().map(|s| s.to_string());
1266        let remove_from = body["RemoveFromVersionId"].as_str().map(|s| s.to_string());
1267
1268        let mut state = self.state.write();
1269        let secret = self.find_secret_mut(&mut state, &secret_id)?;
1270
1271        // Validate: if moving AWSCURRENT, must specify RemoveFromVersionId
1272        if version_stage == "AWSCURRENT" && move_to.is_some() && remove_from.is_none() {
1273            // Find the version that currently has AWSCURRENT
1274            let current_holder = secret
1275                .versions
1276                .iter()
1277                .find(|(_, v)| v.stages.contains(&"AWSCURRENT".to_string()))
1278                .map(|(id, _)| id.clone());
1279
1280            if let Some(current_vid) = current_holder {
1281                return Err(AwsServiceError::aws_error(
1282                    StatusCode::BAD_REQUEST,
1283                    "InvalidParameterException",
1284                    format!(
1285                        "The parameter RemoveFromVersionId can't be empty. Staging label AWSCURRENT is currently attached to version {current_vid}, so you must explicitly reference that version in RemoveFromVersionId."
1286                    ),
1287                ));
1288            }
1289        }
1290
1291        // Remove stage from specified version
1292        if let Some(ref remove_vid) = remove_from {
1293            if let Some(version) = secret.versions.get_mut(remove_vid) {
1294                version.stages.retain(|s| s != &version_stage);
1295                // If moving AWSCURRENT away, add AWSPREVIOUS and remove from others
1296                if version_stage == "AWSCURRENT" {
1297                    // Remove AWSPREVIOUS from all other versions first
1298                    for (id, v) in secret.versions.iter_mut() {
1299                        if id != remove_vid {
1300                            v.stages.retain(|s| s != "AWSPREVIOUS");
1301                        }
1302                    }
1303                    // Now add AWSPREVIOUS to the version losing AWSCURRENT
1304                    if let Some(v) = secret.versions.get_mut(remove_vid) {
1305                        if !v.stages.contains(&"AWSPREVIOUS".to_string()) {
1306                            v.stages.push("AWSPREVIOUS".to_string());
1307                        }
1308                    }
1309                }
1310            }
1311        }
1312
1313        // Add stage to specified version
1314        if let Some(ref move_vid) = move_to {
1315            if let Some(version) = secret.versions.get_mut(move_vid) {
1316                if !version.stages.contains(&version_stage) {
1317                    version.stages.push(version_stage.clone());
1318                }
1319            }
1320            // Update current_version_id if we moved AWSCURRENT
1321            if version_stage == "AWSCURRENT" {
1322                secret.current_version_id = Some(move_vid.clone());
1323            }
1324        }
1325
1326        let response = json!({
1327            "ARN": secret.arn,
1328            "Name": secret.name,
1329        });
1330
1331        Ok(AwsResponse::ok_json(response))
1332    }
1333
1334    fn batch_get_secret_value(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1335        let body = req.json_body();
1336        validate_optional_string_length("nextToken", body["NextToken"].as_str(), 1, 4096)?;
1337        let secret_id_list = body["SecretIdList"].as_array();
1338        let filters = body["Filters"].as_array();
1339        let max_results = body.get("MaxResults").and_then(|v| v.as_i64());
1340
1341        // Validate: can't use both SecretIdList and Filters
1342        if secret_id_list.is_some() && filters.is_some() {
1343            return Err(AwsServiceError::aws_error(
1344                StatusCode::BAD_REQUEST,
1345                "InvalidParameterException",
1346                "Either 'SecretIdList' or 'Filters' must be provided, but not both.",
1347            ));
1348        }
1349
1350        // Validate: MaxResults requires Filters
1351        if max_results.is_some() && filters.is_none() {
1352            return Err(AwsServiceError::aws_error(
1353                StatusCode::BAD_REQUEST,
1354                "InvalidParameterException",
1355                "'Filters' not specified. 'Filters' must also be specified when 'MaxResults' is provided.",
1356            ));
1357        }
1358
1359        let state = self.state.read();
1360        let mut secret_values: Vec<Value> = Vec::new();
1361        let mut errors: Vec<Value> = Vec::new();
1362
1363        if let Some(id_list) = secret_id_list {
1364            for id_val in id_list {
1365                let sid = id_val.as_str().unwrap_or("");
1366                match self.find_secret_ref(&state, sid) {
1367                    Ok(secret) => {
1368                        if secret.deleted {
1369                            errors.push(json!({
1370                                "SecretId": sid,
1371                                "ErrorCode": "InvalidRequestException",
1372                                "Message": "Secret is currently marked deleted. Secret can be recovered with RestoreSecret. Secret is currently marked deleted.",
1373                            }));
1374                        } else if let Some(ref current_vid) = secret.current_version_id {
1375                            if let Some(version) = secret.versions.get(current_vid) {
1376                                let mut entry = json!({
1377                                    "ARN": secret.arn,
1378                                    "Name": secret.name,
1379                                    "VersionId": version.version_id,
1380                                    "VersionStages": version.stages,
1381                                    "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1382                                });
1383                                if let Some(ref s) = version.secret_string {
1384                                    entry["SecretString"] = json!(s);
1385                                }
1386                                if let Some(ref b) = version.secret_binary {
1387                                    entry["SecretBinary"] = json!(base64_encode(b));
1388                                }
1389                                secret_values.push(entry);
1390                            } else {
1391                                errors.push(json!({
1392                                    "SecretId": sid,
1393                                    "ErrorCode": "ResourceNotFoundException",
1394                                    "Message": "Secrets Manager can't find the specified secret.",
1395                                }));
1396                            }
1397                        } else {
1398                            errors.push(json!({
1399                                "SecretId": sid,
1400                                "ErrorCode": "ResourceNotFoundException",
1401                                "Message": "Secrets Manager can't find the specified secret.",
1402                            }));
1403                        }
1404                    }
1405                    Err(_) => {
1406                        errors.push(json!({
1407                            "SecretId": sid,
1408                            "ErrorCode": "ResourceNotFoundException",
1409                            "Message": "Secrets Manager can't find the specified secret.",
1410                        }));
1411                    }
1412                }
1413            }
1414        } else if let Some(filters) = filters {
1415            // Get secrets matching filters
1416            let matching: Vec<&Secret> = state
1417                .secrets
1418                .values()
1419                .filter(|s| {
1420                    if s.deleted {
1421                        return false;
1422                    }
1423                    for filter in filters {
1424                        let key = filter["Key"].as_str().unwrap_or("");
1425                        let values: Vec<&str> = filter["Values"]
1426                            .as_array()
1427                            .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
1428                            .unwrap_or_default();
1429                        let matches = match key {
1430                            "name" => filter_name(s, &values),
1431                            "description" => filter_description(s, &values),
1432                            "tag-key" => filter_tag_key(s, &values),
1433                            "tag-value" => filter_tag_value(s, &values),
1434                            "all" => filter_all(s, &values),
1435                            _ => true,
1436                        };
1437                        if !matches {
1438                            return false;
1439                        }
1440                    }
1441                    true
1442                })
1443                .collect();
1444
1445            let limit = max_results.unwrap_or(100) as usize;
1446            let mut no_value_found = false;
1447            let mut matching = matching;
1448            matching.sort_by(|a, b| a.name.cmp(&b.name));
1449
1450            for secret in matching.iter().take(limit) {
1451                if let Some(ref current_vid) = secret.current_version_id {
1452                    if let Some(version) = secret.versions.get(current_vid) {
1453                        let mut entry = json!({
1454                            "ARN": secret.arn,
1455                            "Name": secret.name,
1456                            "VersionId": version.version_id,
1457                            "VersionStages": version.stages,
1458                            "CreatedDate": version.created_at.timestamp_millis() as f64 / 1000.0,
1459                        });
1460                        if let Some(ref s) = version.secret_string {
1461                            entry["SecretString"] = json!(s);
1462                        }
1463                        if let Some(ref b) = version.secret_binary {
1464                            entry["SecretBinary"] = json!(base64_encode(b));
1465                        }
1466                        secret_values.push(entry);
1467                    } else {
1468                        no_value_found = true;
1469                    }
1470                } else {
1471                    no_value_found = true;
1472                }
1473            }
1474
1475            if no_value_found && secret_values.is_empty() {
1476                return Err(AwsServiceError::aws_error(
1477                    StatusCode::NOT_FOUND,
1478                    "ResourceNotFoundException",
1479                    "Secrets Manager can't find the specified secret.",
1480                ));
1481            }
1482        }
1483
1484        let mut response = json!({
1485            "SecretValues": secret_values,
1486            "Errors": errors,
1487        });
1488
1489        // Remove empty arrays
1490        if errors.is_empty() {
1491            response.as_object_mut().unwrap().remove("Errors");
1492        }
1493
1494        Ok(AwsResponse::ok_json(response))
1495    }
1496
1497    fn get_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1498        let body = req.json_body();
1499        let secret_id = require_secret_id(&body)?;
1500
1501        let state = self.state.read();
1502        let secret = self.find_secret_ref(&state, &secret_id)?;
1503
1504        let mut response = json!({
1505            "ARN": secret.arn,
1506            "Name": secret.name,
1507        });
1508
1509        if let Some(ref policy) = secret.resource_policy {
1510            response["ResourcePolicy"] = json!(policy);
1511        }
1512
1513        Ok(AwsResponse::ok_json(response))
1514    }
1515
1516    fn validate_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1517        let body = req.json_body();
1518        validate_optional_string_length("secretId", body["SecretId"].as_str(), 1, 2048)?;
1519        validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1520        let policy_str = body["ResourcePolicy"].as_str().ok_or_else(|| {
1521            AwsServiceError::aws_error(
1522                StatusCode::BAD_REQUEST,
1523                "InvalidParameterException",
1524                "ResourcePolicy must be a string",
1525            )
1526        })?;
1527        validate_string_length("resourcePolicy", policy_str, 1, 20480)?;
1528
1529        // If SecretId is provided, verify the secret exists
1530        if let Some(secret_id) = body["SecretId"].as_str() {
1531            let state = self.state.read();
1532            self.find_secret_key(&state, secret_id)?;
1533        }
1534
1535        let response = json!({
1536            "PolicyValidationPassed": true,
1537            "ValidationErrors": [],
1538        });
1539        Ok(AwsResponse::ok_json(response))
1540    }
1541
1542    fn put_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1543        let body = req.json_body();
1544        let secret_id = require_secret_id(&body)?;
1545        validate_required("ResourcePolicy", &body["ResourcePolicy"])?;
1546        validate_optional_string_length(
1547            "resourcePolicy",
1548            body["ResourcePolicy"].as_str(),
1549            1,
1550            20480,
1551        )?;
1552        let policy = body["ResourcePolicy"].as_str().map(|s| s.to_string());
1553
1554        let mut state = self.state.write();
1555        let secret = self.find_secret_mut(&mut state, &secret_id)?;
1556        secret.resource_policy = policy;
1557
1558        let response = json!({
1559            "ARN": secret.arn,
1560            "Name": secret.name,
1561        });
1562
1563        Ok(AwsResponse::ok_json(response))
1564    }
1565
1566    fn delete_resource_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1567        let body = req.json_body();
1568        let secret_id = require_secret_id(&body)?;
1569
1570        let mut state = self.state.write();
1571        let secret = self.find_secret_mut(&mut state, &secret_id)?;
1572        secret.resource_policy = None;
1573
1574        let response = json!({
1575            "ARN": secret.arn,
1576            "Name": secret.name,
1577        });
1578
1579        Ok(AwsResponse::ok_json(response))
1580    }
1581
1582    fn replicate_secret_to_regions(
1583        &self,
1584        req: &AwsRequest,
1585    ) -> Result<AwsResponse, AwsServiceError> {
1586        let body = req.json_body();
1587        let secret_id = require_secret_id(&body)?;
1588
1589        let state = self.state.read();
1590        let secret = self.find_secret_ref(&state, &secret_id)?;
1591
1592        let response = json!({
1593            "ARN": secret.arn,
1594            "ReplicationStatus": [],
1595        });
1596        Ok(AwsResponse::ok_json(response))
1597    }
1598
1599    fn remove_regions_from_replication(
1600        &self,
1601        req: &AwsRequest,
1602    ) -> Result<AwsResponse, AwsServiceError> {
1603        let body = req.json_body();
1604        let secret_id = require_secret_id(&body)?;
1605
1606        let state = self.state.read();
1607        let secret = self.find_secret_ref(&state, &secret_id)?;
1608
1609        let response = json!({
1610            "ARN": secret.arn,
1611            "ReplicationStatus": [],
1612        });
1613        Ok(AwsResponse::ok_json(response))
1614    }
1615
1616    fn stop_replication_to_replica(
1617        &self,
1618        req: &AwsRequest,
1619    ) -> Result<AwsResponse, AwsServiceError> {
1620        let body = req.json_body();
1621        let secret_id = require_secret_id(&body)?;
1622
1623        let state = self.state.read();
1624        let secret = self.find_secret_ref(&state, &secret_id)?;
1625
1626        let response = json!({
1627            "ARN": secret.arn,
1628        });
1629        Ok(AwsResponse::ok_json(response))
1630    }
1631
1632    /// Find a secret by name, full ARN, or partial ARN (mutable).
1633    fn find_secret_mut<'a>(
1634        &self,
1635        state: &'a mut crate::state::SecretsManagerState,
1636        secret_id: &str,
1637    ) -> Result<&'a mut Secret, AwsServiceError> {
1638        let key = self.find_secret_key(state, secret_id)?;
1639        Ok(state.secrets.get_mut(&key).unwrap())
1640    }
1641
1642    fn find_secret_key(
1643        &self,
1644        state: &crate::state::SecretsManagerState,
1645        secret_id: &str,
1646    ) -> Result<String, AwsServiceError> {
1647        if state.secrets.contains_key(secret_id) {
1648            return Ok(secret_id.to_string());
1649        }
1650
1651        for secret in state.secrets.values() {
1652            if secret.arn == secret_id {
1653                return Ok(secret.name.clone());
1654            }
1655        }
1656
1657        if secret_id.starts_with("arn:aws:secretsmanager:") {
1658            for secret in state.secrets.values() {
1659                if secret.arn.starts_with(secret_id) {
1660                    return Ok(secret.name.clone());
1661                }
1662            }
1663        }
1664
1665        Err(AwsServiceError::aws_error(
1666            StatusCode::NOT_FOUND,
1667            "ResourceNotFoundException",
1668            "Secrets Manager can't find the specified secret.",
1669        ))
1670    }
1671
1672    /// Find a secret by name, full ARN, or partial ARN (immutable).
1673    fn find_secret_ref<'a>(
1674        &self,
1675        state: &'a crate::state::SecretsManagerState,
1676        secret_id: &str,
1677    ) -> Result<&'a Secret, AwsServiceError> {
1678        if let Some(secret) = state.secrets.get(secret_id) {
1679            return Ok(secret);
1680        }
1681
1682        // Search by full ARN
1683        for secret in state.secrets.values() {
1684            if secret.arn == secret_id {
1685                return Ok(secret);
1686            }
1687        }
1688
1689        // Search by partial ARN
1690        if secret_id.starts_with("arn:aws:secretsmanager:") {
1691            for secret in state.secrets.values() {
1692                if secret.arn.starts_with(secret_id) {
1693                    return Ok(secret);
1694                }
1695            }
1696        }
1697
1698        Err(AwsServiceError::aws_error(
1699            StatusCode::NOT_FOUND,
1700            "ResourceNotFoundException",
1701            "Secrets Manager can't find the specified secret.",
1702        ))
1703    }
1704}
1705
1706fn require_secret_id(body: &Value) -> Result<String, AwsServiceError> {
1707    let id = body["SecretId"].as_str().ok_or_else(|| {
1708        AwsServiceError::aws_error(
1709            StatusCode::BAD_REQUEST,
1710            "InvalidParameterException",
1711            "SecretId is required",
1712        )
1713    })?;
1714    validate_string_length("secretId", id, 1, 2048)?;
1715    Ok(id.to_string())
1716}
1717
1718fn parse_tags(tags_val: &Value) -> Vec<(String, String)> {
1719    tags_val
1720        .as_array()
1721        .map(|arr| {
1722            arr.iter()
1723                .filter_map(|t| {
1724                    let key = t["Key"].as_str()?;
1725                    let value = t["Value"].as_str()?;
1726                    Some((key.to_string(), value.to_string()))
1727                })
1728                .collect()
1729        })
1730        .unwrap_or_default()
1731}
1732
1733fn tags_to_json(tags: &[(String, String)]) -> Vec<Value> {
1734    tags.iter()
1735        .map(|(k, v)| json!({"Key": k, "Value": v}))
1736        .collect()
1737}
1738
1739/// Split text into words for secret name filtering.
1740/// Splits on special characters (/ - _ + = . @) and camelCase.
1741/// If multiple different special characters are present, doesn't split.
1742/// Spaces are always split on first.
1743fn split_words(text: &str) -> Vec<String> {
1744    // First split on whitespace, then apply word splitting to each part
1745    let mut all_words = Vec::new();
1746    for space_part in text.split_whitespace() {
1747        all_words.extend(split_words_no_space(space_part));
1748    }
1749    all_words
1750}
1751
1752fn split_words_no_space(text: &str) -> Vec<String> {
1753    let special_chars = ['/', '-', '_', '+', '=', '.', '@'];
1754
1755    // Check if text is just a special char
1756    if text.len() == 1 && special_chars.contains(&text.chars().next().unwrap_or(' ')) {
1757        return vec![];
1758    }
1759
1760    // Find which special chars are present
1761    let present: Vec<char> = special_chars
1762        .iter()
1763        .filter(|&&c| text.contains(c))
1764        .copied()
1765        .collect();
1766
1767    if present.len() > 1 {
1768        // Multiple different special chars: don't split
1769        return vec![text.to_string()];
1770    }
1771
1772    if present.len() == 1 {
1773        let ch = present[0];
1774        let parts: Vec<&str> = text.split(ch).filter(|s| !s.is_empty()).collect();
1775        let mut result = Vec::new();
1776        for part in parts {
1777            result.extend(split_by_uppercase(part));
1778        }
1779        return result;
1780    }
1781
1782    // No special chars: split by uppercase
1783    split_by_uppercase(text)
1784}
1785
1786/// Split a string by the pattern: a non-lowercase char followed by one or more lowercase chars.
1787/// Equivalent to Python regex: re.split(r"([^a-z][a-z]+)", s)
1788fn split_by_uppercase(text: &str) -> Vec<String> {
1789    // Implement the equivalent of Python's re.split(r"([^a-z][a-z]+)", text)
1790    // re.split with capturing group returns: [before, match, between, match, ..., after]
1791    let chars: Vec<char> = text.chars().collect();
1792    let mut words = Vec::new();
1793    let mut last_end = 0;
1794    let mut i = 0;
1795
1796    while i < chars.len() {
1797        // Try to find pattern: [^a-z][a-z]+
1798        if !chars[i].is_ascii_lowercase()
1799            && i + 1 < chars.len()
1800            && chars[i + 1].is_ascii_lowercase()
1801        {
1802            // Text before this match (between previous match end and this match start)
1803            if i > last_end {
1804                let between: String = chars[last_end..i].iter().collect();
1805                let trimmed = between.trim().to_string();
1806                if !trimmed.is_empty() {
1807                    words.push(trimmed);
1808                }
1809            }
1810
1811            // The match itself
1812            let start = i;
1813            i += 2;
1814            while i < chars.len() && chars[i].is_ascii_lowercase() {
1815                i += 1;
1816            }
1817            let word: String = chars[start..i].iter().collect();
1818            let trimmed = word.trim().to_string();
1819            if !trimmed.is_empty() {
1820                words.push(trimmed);
1821            }
1822            last_end = i;
1823        } else {
1824            i += 1;
1825        }
1826    }
1827
1828    // Text after last match
1829    if last_end < chars.len() {
1830        let after: String = chars[last_end..].iter().collect();
1831        let trimmed = after.trim().to_string();
1832        if !trimmed.is_empty() {
1833            words.push(trimmed);
1834        }
1835    }
1836
1837    words
1838}
1839
1840/// Match a pattern against a value.
1841/// - match_prefix=true: simple prefix match on the full string
1842/// - match_prefix=false: split both into words, all pattern words must prefix-match some value word
1843fn match_pattern(pattern: &str, value: &str, match_prefix: bool, case_sensitive: bool) -> bool {
1844    if match_prefix {
1845        if case_sensitive {
1846            value.starts_with(pattern)
1847        } else {
1848            value.to_lowercase().starts_with(&pattern.to_lowercase())
1849        }
1850    } else {
1851        let mut pattern_words = split_words(pattern);
1852        if pattern_words.is_empty() {
1853            return false;
1854        }
1855        let mut value_words = split_words(value);
1856        if !case_sensitive {
1857            pattern_words = pattern_words.iter().map(|w| w.to_lowercase()).collect();
1858            value_words = value_words.iter().map(|w| w.to_lowercase()).collect();
1859        }
1860        for pw in &pattern_words {
1861            if !value_words.iter().any(|vw| vw.starts_with(pw.as_str())) {
1862                return false;
1863            }
1864        }
1865        true
1866    }
1867}
1868
1869/// The main matcher: check patterns against a list of strings.
1870/// Supports negation (!pattern), prefix matching, and case sensitivity.
1871fn matcher(patterns: &[&str], strings: &[&str], match_prefix: bool, case_sensitive: bool) -> bool {
1872    // First check negated patterns
1873    for pattern in patterns.iter().filter(|p| p.starts_with('!')) {
1874        let inner = &pattern[1..];
1875        for s in strings {
1876            if !match_pattern(inner, s, match_prefix, case_sensitive) {
1877                return true;
1878            }
1879        }
1880    }
1881
1882    // Then check positive patterns
1883    for pattern in patterns.iter().filter(|p| !p.starts_with('!')) {
1884        for s in strings {
1885            if match_pattern(pattern, s, match_prefix, case_sensitive) {
1886                return true;
1887            }
1888        }
1889    }
1890    false
1891}
1892
1893/// Name filter: prefix match, case sensitive
1894fn filter_name(secret: &Secret, values: &[&str]) -> bool {
1895    matcher(values, &[secret.name.as_str()], true, true)
1896}
1897
1898/// Description filter: word match, case insensitive
1899fn filter_description(secret: &Secret, values: &[&str]) -> bool {
1900    match secret.description.as_deref() {
1901        Some(desc) if !desc.is_empty() => matcher(values, &[desc], false, false),
1902        _ => false,
1903    }
1904}
1905
1906/// Tag key filter: prefix match, case sensitive
1907fn filter_tag_key(secret: &Secret, values: &[&str]) -> bool {
1908    if secret.tags.is_empty() {
1909        return false;
1910    }
1911    let keys: Vec<&str> = secret.tags.iter().map(|(k, _)| k.as_str()).collect();
1912    matcher(values, &keys, true, true)
1913}
1914
1915/// Tag value filter: prefix match, case sensitive
1916fn filter_tag_value(secret: &Secret, values: &[&str]) -> bool {
1917    if secret.tags.is_empty() {
1918        return false;
1919    }
1920    let vals: Vec<&str> = secret.tags.iter().map(|(_, v)| v.as_str()).collect();
1921    matcher(values, &vals, true, true)
1922}
1923
1924/// All filter: word match, case insensitive, across all fields
1925fn filter_all(secret: &Secret, values: &[&str]) -> bool {
1926    let mut attributes: Vec<&str> = vec![secret.name.as_str()];
1927    if let Some(ref desc) = secret.description {
1928        if !desc.is_empty() {
1929            attributes.push(desc.as_str());
1930        }
1931    }
1932    for (k, v) in &secret.tags {
1933        attributes.push(k.as_str());
1934        attributes.push(v.as_str());
1935    }
1936    matcher(values, &attributes, false, false)
1937}
1938
1939fn simple_random() -> usize {
1940    use std::collections::hash_map::RandomState;
1941    use std::hash::{BuildHasher, Hasher};
1942    let s = RandomState::new();
1943    let mut hasher = s.build_hasher();
1944    hasher.write_usize(0);
1945    hasher.finish() as usize
1946}
1947
1948#[async_trait]
1949impl AwsService for SecretsManagerService {
1950    fn service_name(&self) -> &str {
1951        "secretsmanager"
1952    }
1953
1954    async fn handle(&self, req: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1955        match req.action.as_str() {
1956            "CreateSecret" => self.create_secret(&req),
1957            "GetSecretValue" => self.get_secret_value(&req),
1958            "PutSecretValue" => self.put_secret_value(&req),
1959            "UpdateSecret" => self.update_secret(&req),
1960            "DeleteSecret" => self.delete_secret(&req),
1961            "RestoreSecret" => self.restore_secret(&req),
1962            "DescribeSecret" => self.describe_secret(&req),
1963            "ListSecrets" => self.list_secrets(&req),
1964            "TagResource" => self.tag_resource(&req),
1965            "UntagResource" => self.untag_resource(&req),
1966            "ListSecretVersionIds" => self.list_secret_version_ids(&req),
1967            "GetRandomPassword" => self.get_random_password(&req),
1968            "RotateSecret" => {
1969                let (response, invocation) = self.rotate_secret(&req)?;
1970                if let Some(inv) = invocation {
1971                    if let Some(ref bus) = self.delivery_bus {
1972                        let bus = bus.clone();
1973                        // AWS invokes the rotation Lambda asynchronously for each step.
1974                        tokio::spawn(async move {
1975                            for step in &["createSecret", "setSecret", "testSecret", "finishSecret"]
1976                            {
1977                                let payload = serde_json::json!({
1978                                    "SecretId": inv.secret_id,
1979                                    "ClientRequestToken": inv.client_request_token,
1980                                    "Step": step,
1981                                });
1982                                let payload_str = payload.to_string();
1983                                match bus.invoke_lambda(&inv.lambda_arn, &payload_str).await {
1984                                    Some(Ok(_)) => {}
1985                                    Some(Err(e)) => {
1986                                        tracing::warn!(
1987                                            step = step,
1988                                            error = %e,
1989                                            "rotation Lambda invocation failed"
1990                                        );
1991                                    }
1992                                    None => {
1993                                        tracing::warn!(
1994                                            lambda_arn = %inv.lambda_arn,
1995                                            step = step,
1996                                            "rotation Lambda delivery not configured; \
1997                                             Lambda invocation skipped"
1998                                        );
1999                                        break;
2000                                    }
2001                                }
2002                            }
2003                        });
2004                    }
2005                }
2006                Ok(response)
2007            }
2008            "CancelRotateSecret" => self.cancel_rotate_secret(&req),
2009            "UpdateSecretVersionStage" => self.update_secret_version_stage(&req),
2010            "BatchGetSecretValue" => self.batch_get_secret_value(&req),
2011            "GetResourcePolicy" => self.get_resource_policy(&req),
2012            "PutResourcePolicy" => self.put_resource_policy(&req),
2013            "DeleteResourcePolicy" => self.delete_resource_policy(&req),
2014            "ValidateResourcePolicy" => self.validate_resource_policy(&req),
2015            "ReplicateSecretToRegions" => self.replicate_secret_to_regions(&req),
2016            "RemoveRegionsFromReplication" => self.remove_regions_from_replication(&req),
2017            "StopReplicationToReplica" => self.stop_replication_to_replica(&req),
2018            _ => Err(AwsServiceError::action_not_implemented(
2019                "secretsmanager",
2020                &req.action,
2021            )),
2022        }
2023    }
2024
2025    fn supported_actions(&self) -> &[&str] {
2026        &[
2027            "CreateSecret",
2028            "GetSecretValue",
2029            "PutSecretValue",
2030            "UpdateSecret",
2031            "DeleteSecret",
2032            "RestoreSecret",
2033            "DescribeSecret",
2034            "ListSecrets",
2035            "TagResource",
2036            "UntagResource",
2037            "ListSecretVersionIds",
2038            "GetRandomPassword",
2039            "RotateSecret",
2040            "CancelRotateSecret",
2041            "UpdateSecretVersionStage",
2042            "BatchGetSecretValue",
2043            "GetResourcePolicy",
2044            "PutResourcePolicy",
2045            "DeleteResourcePolicy",
2046            "ValidateResourcePolicy",
2047            "ReplicateSecretToRegions",
2048            "RemoveRegionsFromReplication",
2049            "StopReplicationToReplica",
2050        ]
2051    }
2052}
2053
2054fn base64_decode(input: &str) -> Option<Vec<u8>> {
2055    let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2056    let mut buf = Vec::new();
2057    let mut bits: u32 = 0;
2058    let mut count = 0;
2059    for &b in input.as_bytes() {
2060        if b == b'=' || b == b'\n' || b == b'\r' {
2061            continue;
2062        }
2063        let val = table.iter().position(|&c| c == b)? as u32;
2064        bits = (bits << 6) | val;
2065        count += 1;
2066        if count == 4 {
2067            buf.push((bits >> 16) as u8);
2068            buf.push((bits >> 8) as u8);
2069            buf.push(bits as u8);
2070            bits = 0;
2071            count = 0;
2072        }
2073    }
2074    match count {
2075        2 => {
2076            bits <<= 12;
2077            buf.push((bits >> 16) as u8);
2078        }
2079        3 => {
2080            bits <<= 6;
2081            buf.push((bits >> 16) as u8);
2082            buf.push((bits >> 8) as u8);
2083        }
2084        _ => {}
2085    }
2086    Some(buf)
2087}
2088
2089fn base64_encode(input: &[u8]) -> String {
2090    let table = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
2091    let mut result = String::new();
2092    for chunk in input.chunks(3) {
2093        let b0 = chunk[0] as u32;
2094        let b1 = if chunk.len() > 1 { chunk[1] as u32 } else { 0 };
2095        let b2 = if chunk.len() > 2 { chunk[2] as u32 } else { 0 };
2096        let triple = (b0 << 16) | (b1 << 8) | b2;
2097        result.push(table[((triple >> 18) & 0x3F) as usize] as char);
2098        result.push(table[((triple >> 12) & 0x3F) as usize] as char);
2099        if chunk.len() > 1 {
2100            result.push(table[((triple >> 6) & 0x3F) as usize] as char);
2101        } else {
2102            result.push('=');
2103        }
2104        if chunk.len() > 2 {
2105            result.push(table[(triple & 0x3F) as usize] as char);
2106        } else {
2107            result.push('=');
2108        }
2109    }
2110    result
2111}
2112
2113#[cfg(test)]
2114mod tests {
2115    use super::*;
2116    use crate::state::SecretsManagerState;
2117    use bytes::Bytes;
2118    use http::{HeaderMap, Method};
2119    use parking_lot::RwLock;
2120    use std::collections::HashMap;
2121    use std::sync::Arc;
2122
2123    fn make_state() -> SharedSecretsManagerState {
2124        Arc::new(RwLock::new(SecretsManagerState::new(
2125            "123456789012",
2126            "us-east-1",
2127        )))
2128    }
2129
2130    fn make_request(action: &str, body: &str) -> AwsRequest {
2131        AwsRequest {
2132            service: "secretsmanager".to_string(),
2133            action: action.to_string(),
2134            region: "us-east-1".to_string(),
2135            account_id: "123456789012".to_string(),
2136            request_id: "test-request-id".to_string(),
2137            headers: HeaderMap::new(),
2138            query_params: HashMap::new(),
2139            body: Bytes::from(body.to_string()),
2140            path_segments: vec![],
2141            raw_path: "/".to_string(),
2142            raw_query: String::new(),
2143            method: Method::POST,
2144            is_query_protocol: false,
2145            access_key_id: None,
2146        }
2147    }
2148
2149    #[tokio::test]
2150    async fn test_create_and_get_secret() {
2151        let state = make_state();
2152        let svc = SecretsManagerService::new(state);
2153
2154        let req = make_request(
2155            "CreateSecret",
2156            r#"{"Name": "test/secret", "SecretString": "mysecretvalue"}"#,
2157        );
2158        let resp = svc.handle(req).await.unwrap();
2159        assert_eq!(resp.status, StatusCode::OK);
2160        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2161        assert_eq!(body["Name"], "test/secret");
2162        assert!(body["ARN"].as_str().unwrap().contains("test/secret"));
2163
2164        let req = make_request("GetSecretValue", r#"{"SecretId": "test/secret"}"#);
2165        let resp = svc.handle(req).await.unwrap();
2166        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2167        assert_eq!(body["SecretString"], "mysecretvalue");
2168    }
2169
2170    #[tokio::test]
2171    async fn test_create_secret_without_value() {
2172        let state = make_state();
2173        let svc = SecretsManagerService::new(state);
2174
2175        let req = make_request("CreateSecret", r#"{"Name": "empty-secret"}"#);
2176        let resp = svc.handle(req).await.unwrap();
2177        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2178        assert_eq!(body["Name"], "empty-secret");
2179        assert!(body.get("VersionId").is_none());
2180    }
2181
2182    #[tokio::test]
2183    async fn test_put_secret_value_creates_version() {
2184        let state = make_state();
2185        let svc = SecretsManagerService::new(state);
2186
2187        let req = make_request(
2188            "CreateSecret",
2189            r#"{"Name": "versioned", "SecretString": "v1"}"#,
2190        );
2191        svc.handle(req).await.unwrap();
2192
2193        let req = make_request(
2194            "PutSecretValue",
2195            r#"{"SecretId": "versioned", "SecretString": "v2"}"#,
2196        );
2197        let resp = svc.handle(req).await.unwrap();
2198        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2199        assert_eq!(body["Name"], "versioned");
2200
2201        // Get should return v2
2202        let req = make_request("GetSecretValue", r#"{"SecretId": "versioned"}"#);
2203        let resp = svc.handle(req).await.unwrap();
2204        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2205        assert_eq!(body["SecretString"], "v2");
2206    }
2207
2208    #[tokio::test]
2209    async fn test_delete_and_restore_secret() {
2210        let state = make_state();
2211        let svc = SecretsManagerService::new(state);
2212
2213        let req = make_request(
2214            "CreateSecret",
2215            r#"{"Name": "deleteme", "SecretString": "value"}"#,
2216        );
2217        svc.handle(req).await.unwrap();
2218
2219        // Delete (soft)
2220        let req = make_request("DeleteSecret", r#"{"SecretId": "deleteme"}"#);
2221        let resp = svc.handle(req).await.unwrap();
2222        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2223        assert!(body["DeletionDate"].as_f64().is_some());
2224
2225        // GetSecretValue should fail
2226        let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2227        assert!(svc.handle(req).await.is_err());
2228
2229        // Restore
2230        let req = make_request("RestoreSecret", r#"{"SecretId": "deleteme"}"#);
2231        let resp = svc.handle(req).await.unwrap();
2232        assert_eq!(resp.status, StatusCode::OK);
2233
2234        // GetSecretValue should work again
2235        let req = make_request("GetSecretValue", r#"{"SecretId": "deleteme"}"#);
2236        let resp = svc.handle(req).await.unwrap();
2237        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2238        assert_eq!(body["SecretString"], "value");
2239    }
2240
2241    #[tokio::test]
2242    async fn test_list_secrets() {
2243        let state = make_state();
2244        let svc = SecretsManagerService::new(state);
2245
2246        for name in &["alpha", "beta", "gamma"] {
2247            let req = make_request(
2248                "CreateSecret",
2249                &format!(r#"{{"Name": "{name}", "SecretString": "val"}}"#),
2250            );
2251            svc.handle(req).await.unwrap();
2252        }
2253
2254        let req = make_request("ListSecrets", "{}");
2255        let resp = svc.handle(req).await.unwrap();
2256        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2257        assert_eq!(body["SecretList"].as_array().unwrap().len(), 3);
2258    }
2259
2260    #[tokio::test]
2261    async fn test_tags() {
2262        let state = make_state();
2263        let svc = SecretsManagerService::new(state);
2264
2265        let req = make_request(
2266            "CreateSecret",
2267            r#"{"Name": "tagged", "SecretString": "val"}"#,
2268        );
2269        svc.handle(req).await.unwrap();
2270
2271        let req = make_request(
2272            "TagResource",
2273            r#"{"SecretId": "tagged", "Tags": [{"Key": "env", "Value": "prod"}]}"#,
2274        );
2275        svc.handle(req).await.unwrap();
2276
2277        let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2278        let resp = svc.handle(req).await.unwrap();
2279        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2280        let tags = body["Tags"].as_array().unwrap();
2281        assert!(tags
2282            .iter()
2283            .any(|t| t["Key"] == "env" && t["Value"] == "prod"));
2284
2285        let req = make_request(
2286            "UntagResource",
2287            r#"{"SecretId": "tagged", "TagKeys": ["env"]}"#,
2288        );
2289        svc.handle(req).await.unwrap();
2290
2291        let req = make_request("DescribeSecret", r#"{"SecretId": "tagged"}"#);
2292        let resp = svc.handle(req).await.unwrap();
2293        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2294        // Tags should be empty list after untagging all (but key present since tags were set)
2295        assert_eq!(body["Tags"].as_array().unwrap().len(), 0);
2296    }
2297
2298    #[tokio::test]
2299    async fn test_get_random_password() {
2300        let state = make_state();
2301        let svc = SecretsManagerService::new(state);
2302
2303        let req = make_request("GetRandomPassword", "{}");
2304        let resp = svc.handle(req).await.unwrap();
2305        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2306        assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 32);
2307    }
2308
2309    #[tokio::test]
2310    async fn test_replication_ops_return_arn() {
2311        let state = make_state();
2312        let svc = SecretsManagerService::new(state);
2313
2314        let req = make_request(
2315            "CreateSecret",
2316            r#"{"Name": "repl-secret", "SecretString": "val"}"#,
2317        );
2318        let resp = svc.handle(req).await.unwrap();
2319        let create_body: Value = serde_json::from_slice(&resp.body).unwrap();
2320        let expected_arn = create_body["ARN"].as_str().unwrap();
2321
2322        for action in &[
2323            "ReplicateSecretToRegions",
2324            "RemoveRegionsFromReplication",
2325            "StopReplicationToReplica",
2326        ] {
2327            let req = make_request(action, r#"{"SecretId": "repl-secret"}"#);
2328            let resp = svc.handle(req).await.unwrap();
2329            let body: Value = serde_json::from_slice(&resp.body).unwrap();
2330            assert_eq!(
2331                body["ARN"].as_str().unwrap(),
2332                expected_arn,
2333                "{action} should return the secret's actual ARN"
2334            );
2335        }
2336    }
2337
2338    #[tokio::test]
2339    async fn test_secret_id_length_validation() {
2340        let state = make_state();
2341        let svc = SecretsManagerService::new(state);
2342
2343        // SecretId too long (> 2048)
2344        let long_id = "x".repeat(2049);
2345        let req = make_request("GetSecretValue", &format!(r#"{{"SecretId": "{long_id}"}}"#));
2346        match svc.handle(req).await {
2347            Err(e) => assert!(e.to_string().contains("ValidationException")),
2348            Ok(_) => panic!("expected ValidationException"),
2349        }
2350    }
2351
2352    #[tokio::test]
2353    async fn test_name_length_validation() {
2354        let state = make_state();
2355        let svc = SecretsManagerService::new(state);
2356
2357        // Name too long (> 512)
2358        let long_name = "x".repeat(513);
2359        let req = make_request(
2360            "CreateSecret",
2361            &format!(r#"{{"Name": "{long_name}", "SecretString": "val"}}"#),
2362        );
2363        match svc.handle(req).await {
2364            Err(e) => assert!(e.to_string().contains("ValidationException")),
2365            Ok(_) => panic!("expected ValidationException"),
2366        }
2367    }
2368
2369    #[tokio::test]
2370    async fn test_next_token_length_validation() {
2371        let state = make_state();
2372        let svc = SecretsManagerService::new(state);
2373
2374        // NextToken too long (> 4096)
2375        let long_token = "x".repeat(4097);
2376        let req = make_request(
2377            "ListSecrets",
2378            &format!(r#"{{"NextToken": "{long_token}"}}"#),
2379        );
2380        match svc.handle(req).await {
2381            Err(e) => assert!(e.to_string().contains("ValidationException")),
2382            Ok(_) => panic!("expected ValidationException"),
2383        }
2384    }
2385
2386    #[tokio::test]
2387    async fn test_client_request_token_length_validation() {
2388        let state = make_state();
2389        let svc = SecretsManagerService::new(state);
2390
2391        // ClientRequestToken too short (< 32)
2392        let req = make_request(
2393            "CreateSecret",
2394            r#"{"Name": "test", "SecretString": "val", "ClientRequestToken": "short"}"#,
2395        );
2396        match svc.handle(req).await {
2397            Err(e) => assert!(e.to_string().contains("ValidationException")),
2398            Ok(_) => panic!("expected ValidationException"),
2399        }
2400    }
2401
2402    #[tokio::test]
2403    async fn test_rotate_secret_with_lambda_creates_pending_version() {
2404        let state = make_state();
2405        let svc = SecretsManagerService::new(state.clone());
2406
2407        // Create a secret
2408        let req = make_request(
2409            "CreateSecret",
2410            r#"{"Name": "rotate-me", "SecretString": "old-password"}"#,
2411        );
2412        svc.handle(req).await.unwrap();
2413
2414        // Rotate with a Lambda ARN
2415        let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2416        let body = serde_json::json!({
2417            "SecretId": "rotate-me",
2418            "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:rotator",
2419            "ClientRequestToken": token,
2420        });
2421        let req = make_request("RotateSecret", &body.to_string());
2422        let resp = svc.handle(req).await.unwrap();
2423        assert_eq!(resp.status, StatusCode::OK);
2424        let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
2425        assert_eq!(resp_body["VersionId"], token);
2426
2427        // Verify AWSPENDING version exists
2428        let s = state.read();
2429        let secret = s.secrets.get("rotate-me").unwrap();
2430        let pending = secret.versions.get(token).unwrap();
2431        assert!(pending.stages.contains(&"AWSPENDING".to_string()));
2432        assert_eq!(pending.secret_string.as_deref(), Some("old-password"));
2433
2434        // Verify rotation config was set
2435        assert_eq!(
2436            secret.rotation_lambda_arn.as_deref(),
2437            Some("arn:aws:lambda:us-east-1:123456789012:function:rotator")
2438        );
2439        assert_eq!(secret.rotation_enabled, Some(true));
2440    }
2441
2442    #[tokio::test]
2443    async fn test_rotate_secret_without_lambda_promotes_directly() {
2444        let state = make_state();
2445        let svc = SecretsManagerService::new(state.clone());
2446
2447        // Create a secret
2448        let req = make_request(
2449            "CreateSecret",
2450            r#"{"Name": "rotate-no-lambda", "SecretString": "value1"}"#,
2451        );
2452        svc.handle(req).await.unwrap();
2453
2454        // Rotate without Lambda ARN
2455        let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2456        let body = serde_json::json!({
2457            "SecretId": "rotate-no-lambda",
2458            "ClientRequestToken": token,
2459        });
2460        let req = make_request("RotateSecret", &body.to_string());
2461        svc.handle(req).await.unwrap();
2462
2463        // Verify the new version is AWSCURRENT (no pending)
2464        let s = state.read();
2465        let secret = s.secrets.get("rotate-no-lambda").unwrap();
2466        let new_ver = secret.versions.get(token).unwrap();
2467        assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2468        assert_eq!(secret.current_version_id.as_deref(), Some(token));
2469    }
2470
2471    #[tokio::test]
2472    async fn test_rotate_secret_stores_rotation_config() {
2473        let state = make_state();
2474        let svc = SecretsManagerService::new(state.clone());
2475
2476        let req = make_request(
2477            "CreateSecret",
2478            r#"{"Name": "rot-cfg", "SecretString": "pw"}"#,
2479        );
2480        svc.handle(req).await.unwrap();
2481
2482        let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2483        let body = serde_json::json!({
2484            "SecretId": "rot-cfg",
2485            "RotationLambdaARN": "arn:aws:lambda:us-east-1:123456789012:function:my-rotator",
2486            "RotationRules": { "AutomaticallyAfterDays": 30 },
2487            "ClientRequestToken": token,
2488        });
2489        let req = make_request("RotateSecret", &body.to_string());
2490        let resp = svc.handle(req).await.unwrap();
2491        assert_eq!(resp.status, StatusCode::OK);
2492
2493        let s = state.read();
2494        let secret = s.secrets.get("rot-cfg").unwrap();
2495        assert_eq!(secret.rotation_enabled, Some(true));
2496        assert_eq!(
2497            secret.rotation_lambda_arn.as_deref(),
2498            Some("arn:aws:lambda:us-east-1:123456789012:function:my-rotator")
2499        );
2500        assert!(secret.last_rotated_at.is_some());
2501        let rules = secret.rotation_rules.as_ref().unwrap();
2502        assert_eq!(rules.automatically_after_days, Some(30));
2503
2504        // Verify AWSPENDING version was created (Lambda ARN present)
2505        let pending = secret.versions.get(token).unwrap();
2506        assert!(pending.stages.contains(&"AWSPENDING".to_string()));
2507    }
2508
2509    #[tokio::test]
2510    async fn test_rotate_secret_version_stages_change() {
2511        let state = make_state();
2512        let svc = SecretsManagerService::new(state.clone());
2513
2514        let req = make_request(
2515            "CreateSecret",
2516            r#"{"Name": "rot-stages", "SecretString": "original"}"#,
2517        );
2518        svc.handle(req).await.unwrap();
2519
2520        // Get original version id
2521        let original_vid = {
2522            let s = state.read();
2523            let secret = s.secrets.get("rot-stages").unwrap();
2524            secret.current_version_id.clone().unwrap()
2525        };
2526
2527        // Rotate without Lambda (simple rotation)
2528        let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2529        let body = serde_json::json!({
2530            "SecretId": "rot-stages",
2531            "ClientRequestToken": token,
2532        });
2533        let req = make_request("RotateSecret", &body.to_string());
2534        svc.handle(req).await.unwrap();
2535
2536        let s = state.read();
2537        let secret = s.secrets.get("rot-stages").unwrap();
2538
2539        // New version should be AWSCURRENT
2540        let new_ver = secret.versions.get(token).unwrap();
2541        assert!(new_ver.stages.contains(&"AWSCURRENT".to_string()));
2542
2543        // Old version should be AWSPREVIOUS
2544        let old_ver = secret.versions.get(&original_vid).unwrap();
2545        assert!(old_ver.stages.contains(&"AWSPREVIOUS".to_string()));
2546        assert!(!old_ver.stages.contains(&"AWSCURRENT".to_string()));
2547    }
2548
2549    #[tokio::test]
2550    async fn test_cancel_rotate_secret() {
2551        let state = make_state();
2552        let svc = SecretsManagerService::new(state.clone());
2553
2554        let req = make_request(
2555            "CreateSecret",
2556            r#"{"Name": "cancel-rot", "SecretString": "pw"}"#,
2557        );
2558        svc.handle(req).await.unwrap();
2559
2560        // Enable rotation first
2561        let token = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee";
2562        let body = serde_json::json!({
2563            "SecretId": "cancel-rot",
2564            "ClientRequestToken": token,
2565        });
2566        let req = make_request("RotateSecret", &body.to_string());
2567        svc.handle(req).await.unwrap();
2568
2569        // Verify rotation is enabled
2570        {
2571            let s = state.read();
2572            let secret = s.secrets.get("cancel-rot").unwrap();
2573            assert_eq!(secret.rotation_enabled, Some(true));
2574        }
2575
2576        // Cancel rotation
2577        let req = make_request("CancelRotateSecret", r#"{"SecretId": "cancel-rot"}"#);
2578        let resp = svc.handle(req).await.unwrap();
2579        assert_eq!(resp.status, StatusCode::OK);
2580        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2581        assert_eq!(body["Name"], "cancel-rot");
2582
2583        // Verify rotation is disabled
2584        let s = state.read();
2585        let secret = s.secrets.get("cancel-rot").unwrap();
2586        assert_eq!(secret.rotation_enabled, Some(false));
2587    }
2588
2589    #[tokio::test]
2590    async fn test_cancel_rotate_secret_fails_when_not_enabled() {
2591        let state = make_state();
2592        let svc = SecretsManagerService::new(state);
2593
2594        let req = make_request(
2595            "CreateSecret",
2596            r#"{"Name": "no-rot", "SecretString": "pw"}"#,
2597        );
2598        svc.handle(req).await.unwrap();
2599
2600        let req = make_request("CancelRotateSecret", r#"{"SecretId": "no-rot"}"#);
2601        let result = svc.handle(req).await;
2602        assert!(result.is_err());
2603    }
2604
2605    #[tokio::test]
2606    async fn test_batch_get_secret_value_multiple() {
2607        let state = make_state();
2608        let svc = SecretsManagerService::new(state);
2609
2610        for (name, val) in &[("batch-a", "va"), ("batch-b", "vb"), ("batch-c", "vc")] {
2611            let req = make_request(
2612                "CreateSecret",
2613                &format!(r#"{{"Name": "{name}", "SecretString": "{val}"}}"#),
2614            );
2615            svc.handle(req).await.unwrap();
2616        }
2617
2618        let body = serde_json::json!({
2619            "SecretIdList": ["batch-a", "batch-b", "batch-c"]
2620        });
2621        let req = make_request("BatchGetSecretValue", &body.to_string());
2622        let resp = svc.handle(req).await.unwrap();
2623        let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
2624
2625        let values = resp_body["SecretValues"].as_array().unwrap();
2626        assert_eq!(values.len(), 3);
2627
2628        // Verify each secret has the right value
2629        let names: Vec<&str> = values.iter().map(|v| v["Name"].as_str().unwrap()).collect();
2630        assert!(names.contains(&"batch-a"));
2631        assert!(names.contains(&"batch-b"));
2632        assert!(names.contains(&"batch-c"));
2633
2634        // Verify no errors
2635        assert!(resp_body.get("Errors").is_none());
2636    }
2637
2638    #[tokio::test]
2639    async fn test_batch_get_secret_value_with_missing() {
2640        let state = make_state();
2641        let svc = SecretsManagerService::new(state);
2642
2643        let req = make_request(
2644            "CreateSecret",
2645            r#"{"Name": "exists", "SecretString": "val"}"#,
2646        );
2647        svc.handle(req).await.unwrap();
2648
2649        let body = serde_json::json!({
2650            "SecretIdList": ["exists", "nonexistent"]
2651        });
2652        let req = make_request("BatchGetSecretValue", &body.to_string());
2653        let resp = svc.handle(req).await.unwrap();
2654        let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
2655
2656        let values = resp_body["SecretValues"].as_array().unwrap();
2657        assert_eq!(values.len(), 1);
2658        assert_eq!(values[0]["Name"], "exists");
2659
2660        let errors = resp_body["Errors"].as_array().unwrap();
2661        assert_eq!(errors.len(), 1);
2662        assert_eq!(errors[0]["SecretId"], "nonexistent");
2663        assert_eq!(errors[0]["ErrorCode"], "ResourceNotFoundException");
2664    }
2665
2666    #[tokio::test]
2667    async fn test_update_secret_changes_description_and_kms() {
2668        let state = make_state();
2669        let svc = SecretsManagerService::new(state);
2670
2671        let req = make_request(
2672            "CreateSecret",
2673            r#"{"Name": "updatable", "SecretString": "val", "Description": "old desc"}"#,
2674        );
2675        svc.handle(req).await.unwrap();
2676
2677        // Update description and KmsKeyId
2678        let body = serde_json::json!({
2679            "SecretId": "updatable",
2680            "Description": "new desc",
2681            "KmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/my-key"
2682        });
2683        let req = make_request("UpdateSecret", &body.to_string());
2684        let resp = svc.handle(req).await.unwrap();
2685        assert_eq!(resp.status, StatusCode::OK);
2686        let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
2687        assert_eq!(resp_body["Name"], "updatable");
2688        // No VersionId since no new value was provided
2689        assert!(resp_body.get("VersionId").is_none());
2690
2691        // Describe to verify changes
2692        let req = make_request("DescribeSecret", r#"{"SecretId": "updatable"}"#);
2693        let resp = svc.handle(req).await.unwrap();
2694        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2695        assert_eq!(body["Description"], "new desc");
2696        assert_eq!(
2697            body["KmsKeyId"],
2698            "arn:aws:kms:us-east-1:123456789012:key/my-key"
2699        );
2700    }
2701
2702    #[tokio::test]
2703    async fn test_update_secret_with_new_value() {
2704        let state = make_state();
2705        let svc = SecretsManagerService::new(state);
2706
2707        let req = make_request(
2708            "CreateSecret",
2709            r#"{"Name": "upd-val", "SecretString": "old"}"#,
2710        );
2711        svc.handle(req).await.unwrap();
2712
2713        // Update with a new value
2714        let body = serde_json::json!({
2715            "SecretId": "upd-val",
2716            "SecretString": "new-value"
2717        });
2718        let req = make_request("UpdateSecret", &body.to_string());
2719        let resp = svc.handle(req).await.unwrap();
2720        let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
2721        assert!(resp_body["VersionId"].as_str().is_some());
2722
2723        // Get should return new value
2724        let req = make_request("GetSecretValue", r#"{"SecretId": "upd-val"}"#);
2725        let resp = svc.handle(req).await.unwrap();
2726        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2727        assert_eq!(body["SecretString"], "new-value");
2728    }
2729
2730    #[tokio::test]
2731    async fn test_get_random_password_custom_length() {
2732        let state = make_state();
2733        let svc = SecretsManagerService::new(state);
2734
2735        let req = make_request("GetRandomPassword", r#"{"PasswordLength": 64}"#);
2736        let resp = svc.handle(req).await.unwrap();
2737        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2738        assert_eq!(body["RandomPassword"].as_str().unwrap().len(), 64);
2739    }
2740
2741    #[tokio::test]
2742    async fn test_get_random_password_exclude_chars() {
2743        let state = make_state();
2744        let svc = SecretsManagerService::new(state);
2745
2746        let req = make_request(
2747            "GetRandomPassword",
2748            r#"{"PasswordLength": 100, "ExcludeCharacters": "abc123"}"#,
2749        );
2750        let resp = svc.handle(req).await.unwrap();
2751        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2752        let password = body["RandomPassword"].as_str().unwrap();
2753        assert_eq!(password.len(), 100);
2754        assert!(!password.contains('a'));
2755        assert!(!password.contains('b'));
2756        assert!(!password.contains('c'));
2757        assert!(!password.contains('1'));
2758        assert!(!password.contains('2'));
2759        assert!(!password.contains('3'));
2760    }
2761
2762    #[tokio::test]
2763    async fn test_get_random_password_exclude_types() {
2764        let state = make_state();
2765        let svc = SecretsManagerService::new(state);
2766
2767        // Exclude everything except lowercase
2768        let body = serde_json::json!({
2769            "PasswordLength": 50,
2770            "ExcludeUppercase": true,
2771            "ExcludeNumbers": true,
2772            "ExcludePunctuation": true,
2773            "RequireEachIncludedType": false,
2774        });
2775        let req = make_request("GetRandomPassword", &body.to_string());
2776        let resp = svc.handle(req).await.unwrap();
2777        let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
2778        let password = resp_body["RandomPassword"].as_str().unwrap();
2779        assert_eq!(password.len(), 50);
2780        assert!(password.chars().all(|c| c.is_ascii_lowercase()));
2781    }
2782
2783    #[tokio::test]
2784    async fn test_get_random_password_too_short() {
2785        let state = make_state();
2786        let svc = SecretsManagerService::new(state);
2787
2788        let req = make_request("GetRandomPassword", r#"{"PasswordLength": 3}"#);
2789        assert!(svc.handle(req).await.is_err());
2790    }
2791
2792    #[tokio::test]
2793    async fn test_get_random_password_too_long() {
2794        let state = make_state();
2795        let svc = SecretsManagerService::new(state);
2796
2797        let req = make_request("GetRandomPassword", r#"{"PasswordLength": 4097}"#);
2798        assert!(svc.handle(req).await.is_err());
2799    }
2800
2801    #[tokio::test]
2802    async fn test_update_secret_version_stage_move_current() {
2803        let state = make_state();
2804        let svc = SecretsManagerService::new(state.clone());
2805
2806        let req = make_request(
2807            "CreateSecret",
2808            r#"{"Name": "stage-test", "SecretString": "v1"}"#,
2809        );
2810        svc.handle(req).await.unwrap();
2811
2812        // Put a second version
2813        let req = make_request(
2814            "PutSecretValue",
2815            r#"{"SecretId": "stage-test", "SecretString": "v2"}"#,
2816        );
2817        svc.handle(req).await.unwrap();
2818
2819        // Get version IDs
2820        let (v1_id, v2_id) = {
2821            let s = state.read();
2822            let secret = s.secrets.get("stage-test").unwrap();
2823            let current = secret.current_version_id.clone().unwrap();
2824            let previous = secret
2825                .versions
2826                .iter()
2827                .find(|(id, _)| **id != current)
2828                .map(|(id, _)| id.clone())
2829                .unwrap();
2830            (previous, current)
2831        };
2832
2833        // Move AWSCURRENT from v2 back to v1
2834        let body = serde_json::json!({
2835            "SecretId": "stage-test",
2836            "VersionStage": "AWSCURRENT",
2837            "MoveToVersionId": v1_id,
2838            "RemoveFromVersionId": v2_id,
2839        });
2840        let req = make_request("UpdateSecretVersionStage", &body.to_string());
2841        let resp = svc.handle(req).await.unwrap();
2842        assert_eq!(resp.status, StatusCode::OK);
2843
2844        // Verify v1 is now AWSCURRENT
2845        let s = state.read();
2846        let secret = s.secrets.get("stage-test").unwrap();
2847        let v1 = secret.versions.get(&v1_id).unwrap();
2848        assert!(v1.stages.contains(&"AWSCURRENT".to_string()));
2849
2850        // v2 should have AWSPREVIOUS
2851        let v2 = secret.versions.get(&v2_id).unwrap();
2852        assert!(v2.stages.contains(&"AWSPREVIOUS".to_string()));
2853        assert!(!v2.stages.contains(&"AWSCURRENT".to_string()));
2854
2855        assert_eq!(secret.current_version_id.as_deref(), Some(v1_id.as_str()));
2856    }
2857
2858    #[tokio::test]
2859    async fn test_update_secret_version_stage_custom_label() {
2860        let state = make_state();
2861        let svc = SecretsManagerService::new(state.clone());
2862
2863        let req = make_request(
2864            "CreateSecret",
2865            r#"{"Name": "custom-stage", "SecretString": "v1"}"#,
2866        );
2867        svc.handle(req).await.unwrap();
2868
2869        let vid = {
2870            let s = state.read();
2871            s.secrets
2872                .get("custom-stage")
2873                .unwrap()
2874                .current_version_id
2875                .clone()
2876                .unwrap()
2877        };
2878
2879        // Add a custom label
2880        let body = serde_json::json!({
2881            "SecretId": "custom-stage",
2882            "VersionStage": "MYAPP_LIVE",
2883            "MoveToVersionId": vid,
2884        });
2885        let req = make_request("UpdateSecretVersionStage", &body.to_string());
2886        svc.handle(req).await.unwrap();
2887
2888        let s = state.read();
2889        let secret = s.secrets.get("custom-stage").unwrap();
2890        let ver = secret.versions.get(&vid).unwrap();
2891        assert!(ver.stages.contains(&"MYAPP_LIVE".to_string()));
2892        assert!(ver.stages.contains(&"AWSCURRENT".to_string()));
2893    }
2894
2895    #[tokio::test]
2896    async fn test_validate_resource_policy() {
2897        let state = make_state();
2898        let svc = SecretsManagerService::new(state);
2899
2900        let policy = serde_json::json!({
2901            "Version": "2012-10-17",
2902            "Statement": [{
2903                "Effect": "Allow",
2904                "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
2905                "Action": "secretsmanager:GetSecretValue",
2906                "Resource": "*"
2907            }]
2908        });
2909
2910        let body = serde_json::json!({
2911            "ResourcePolicy": policy.to_string(),
2912        });
2913        let req = make_request("ValidateResourcePolicy", &body.to_string());
2914        let resp = svc.handle(req).await.unwrap();
2915        let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
2916        assert_eq!(resp_body["PolicyValidationPassed"], true);
2917        assert_eq!(resp_body["ValidationErrors"].as_array().unwrap().len(), 0);
2918    }
2919
2920    #[tokio::test]
2921    async fn test_validate_resource_policy_requires_policy() {
2922        let state = make_state();
2923        let svc = SecretsManagerService::new(state);
2924
2925        let req = make_request("ValidateResourcePolicy", r#"{}"#);
2926        assert!(svc.handle(req).await.is_err());
2927    }
2928
2929    #[tokio::test]
2930    async fn test_put_get_delete_resource_policy() {
2931        let state = make_state();
2932        let svc = SecretsManagerService::new(state);
2933
2934        let req = make_request(
2935            "CreateSecret",
2936            r#"{"Name": "policy-secret", "SecretString": "val"}"#,
2937        );
2938        svc.handle(req).await.unwrap();
2939
2940        // Get policy (should be empty initially)
2941        let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
2942        let resp = svc.handle(req).await.unwrap();
2943        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2944        assert_eq!(body["Name"], "policy-secret");
2945        assert!(body.get("ResourcePolicy").is_none());
2946
2947        // Put policy
2948        let policy = r#"{"Version":"2012-10-17","Statement":[]}"#;
2949        let put_body = serde_json::json!({
2950            "SecretId": "policy-secret",
2951            "ResourcePolicy": policy,
2952        });
2953        let req = make_request("PutResourcePolicy", &put_body.to_string());
2954        let resp = svc.handle(req).await.unwrap();
2955        assert_eq!(resp.status, StatusCode::OK);
2956
2957        // Get policy (should have it now)
2958        let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
2959        let resp = svc.handle(req).await.unwrap();
2960        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2961        assert_eq!(body["ResourcePolicy"], policy);
2962
2963        // Delete policy
2964        let req = make_request("DeleteResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
2965        let resp = svc.handle(req).await.unwrap();
2966        assert_eq!(resp.status, StatusCode::OK);
2967
2968        // Get again (should be gone)
2969        let req = make_request("GetResourcePolicy", r#"{"SecretId": "policy-secret"}"#);
2970        let resp = svc.handle(req).await.unwrap();
2971        let body: Value = serde_json::from_slice(&resp.body).unwrap();
2972        assert!(body.get("ResourcePolicy").is_none());
2973    }
2974
2975    #[tokio::test]
2976    async fn test_batch_get_secret_value_with_deleted() {
2977        let state = make_state();
2978        let svc = SecretsManagerService::new(state);
2979
2980        let req = make_request(
2981            "CreateSecret",
2982            r#"{"Name": "batch-del", "SecretString": "val"}"#,
2983        );
2984        svc.handle(req).await.unwrap();
2985
2986        // Soft-delete it
2987        let req = make_request("DeleteSecret", r#"{"SecretId": "batch-del"}"#);
2988        svc.handle(req).await.unwrap();
2989
2990        let body = serde_json::json!({
2991            "SecretIdList": ["batch-del"]
2992        });
2993        let req = make_request("BatchGetSecretValue", &body.to_string());
2994        let resp = svc.handle(req).await.unwrap();
2995        let resp_body: Value = serde_json::from_slice(&resp.body).unwrap();
2996
2997        // Should have 0 values and 1 error
2998        assert_eq!(resp_body["SecretValues"].as_array().unwrap().len(), 0);
2999        let errors = resp_body["Errors"].as_array().unwrap();
3000        assert_eq!(errors.len(), 1);
3001        assert_eq!(errors[0]["ErrorCode"], "InvalidRequestException");
3002    }
3003}