Skip to main content

fakecloud_ssoadmin/
service.rs

1//! SSO Admin (`sso-admin`) awsJson1.1 dispatch + operation handlers.
2//!
3//! The full 79-operation control plane. State is account-partitioned and
4//! persisted. Nested configuration objects (PortalOptions, Grant,
5//! TrustedTokenIssuerConfiguration, ...) are stored as the raw request `Value`
6//! so they round-trip verbatim.
7
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use http::StatusCode;
12use serde_json::{json, Value};
13use tokio::sync::Mutex as AsyncMutex;
14use uuid::Uuid;
15
16use fakecloud_core::service::{AwsRequest, AwsResponse, AwsService, AwsServiceError};
17use fakecloud_persistence::SnapshotStore;
18
19use crate::persistence::save_snapshot;
20use crate::state::{
21    SharedSsoAdminState, StoredApplication, StoredAssignment, StoredInstance, StoredManagedPolicy,
22    StoredOpStatus, StoredPermissionSet, StoredProvisioningStatus, StoredRegion,
23    StoredTrustedTokenIssuer,
24};
25
26/// Every operation name in the SSO Admin Smithy model.
27pub const SSOADMIN_ACTIONS: &[&str] = &[
28    "AddRegion",
29    "AttachCustomerManagedPolicyReferenceToPermissionSet",
30    "AttachManagedPolicyToPermissionSet",
31    "CreateAccountAssignment",
32    "CreateApplication",
33    "CreateApplicationAssignment",
34    "CreateInstance",
35    "CreateInstanceAccessControlAttributeConfiguration",
36    "CreatePermissionSet",
37    "CreateTrustedTokenIssuer",
38    "DeleteAccountAssignment",
39    "DeleteApplication",
40    "DeleteApplicationAccessScope",
41    "DeleteApplicationAssignment",
42    "DeleteApplicationAuthenticationMethod",
43    "DeleteApplicationGrant",
44    "DeleteInlinePolicyFromPermissionSet",
45    "DeleteInstance",
46    "DeleteInstanceAccessControlAttributeConfiguration",
47    "DeletePermissionSet",
48    "DeletePermissionsBoundaryFromPermissionSet",
49    "DeleteTrustedTokenIssuer",
50    "DescribeAccountAssignmentCreationStatus",
51    "DescribeAccountAssignmentDeletionStatus",
52    "DescribeApplication",
53    "DescribeApplicationAssignment",
54    "DescribeApplicationProvider",
55    "DescribeInstance",
56    "DescribeInstanceAccessControlAttributeConfiguration",
57    "DescribePermissionSet",
58    "DescribePermissionSetProvisioningStatus",
59    "DescribeRegion",
60    "DescribeTrustedTokenIssuer",
61    "DetachCustomerManagedPolicyReferenceFromPermissionSet",
62    "DetachManagedPolicyFromPermissionSet",
63    "GetApplicationAccessScope",
64    "GetApplicationAssignmentConfiguration",
65    "GetApplicationAuthenticationMethod",
66    "GetApplicationGrant",
67    "GetApplicationSessionConfiguration",
68    "GetInlinePolicyForPermissionSet",
69    "GetPermissionsBoundaryForPermissionSet",
70    "ListAccountAssignmentCreationStatus",
71    "ListAccountAssignmentDeletionStatus",
72    "ListAccountAssignments",
73    "ListAccountAssignmentsForPrincipal",
74    "ListAccountsForProvisionedPermissionSet",
75    "ListApplicationAccessScopes",
76    "ListApplicationAssignments",
77    "ListApplicationAssignmentsForPrincipal",
78    "ListApplicationAuthenticationMethods",
79    "ListApplicationGrants",
80    "ListApplicationProviders",
81    "ListApplications",
82    "ListCustomerManagedPolicyReferencesInPermissionSet",
83    "ListInstances",
84    "ListManagedPoliciesInPermissionSet",
85    "ListPermissionSetProvisioningStatus",
86    "ListPermissionSets",
87    "ListPermissionSetsProvisionedToAccount",
88    "ListRegions",
89    "ListTagsForResource",
90    "ListTrustedTokenIssuers",
91    "ProvisionPermissionSet",
92    "PutApplicationAccessScope",
93    "PutApplicationAssignmentConfiguration",
94    "PutApplicationAuthenticationMethod",
95    "PutApplicationGrant",
96    "PutApplicationSessionConfiguration",
97    "PutInlinePolicyToPermissionSet",
98    "PutPermissionsBoundaryToPermissionSet",
99    "RemoveRegion",
100    "TagResource",
101    "UntagResource",
102    "UpdateApplication",
103    "UpdateInstance",
104    "UpdateInstanceAccessControlAttributeConfiguration",
105    "UpdatePermissionSet",
106    "UpdateTrustedTokenIssuer",
107];
108
109/// The fixed AWS application-provider catalogue. IAM Identity Center exposes a
110/// closed set of managed application providers; we surface a representative
111/// subset with their real ARNs and federation protocol.
112const APPLICATION_PROVIDERS: &[(&str, &str, &str)] = &[
113    (
114        "arn:aws:sso::aws:applicationProvider/custom",
115        "OAUTH",
116        "Custom application",
117    ),
118    (
119        "arn:aws:sso::aws:applicationProvider/sso",
120        "SAML",
121        "IAM Identity Center",
122    ),
123    (
124        "arn:aws:sso::aws:applicationProvider/salesforce",
125        "SAML",
126        "Salesforce",
127    ),
128    ("arn:aws:sso::aws:applicationProvider/box", "SAML", "Box"),
129    (
130        "arn:aws:sso::aws:applicationProvider/slack",
131        "SAML",
132        "Slack",
133    ),
134    (
135        "arn:aws:sso::aws:applicationProvider/google",
136        "SAML",
137        "Google Workspace",
138    ),
139    (
140        "arn:aws:sso::aws:applicationProvider/microsoft365",
141        "SAML",
142        "Microsoft 365",
143    ),
144];
145
146pub struct SsoAdminService {
147    state: SharedSsoAdminState,
148    snapshot_store: Option<Arc<dyn SnapshotStore>>,
149    snapshot_lock: Arc<AsyncMutex<()>>,
150}
151
152impl SsoAdminService {
153    pub fn new(state: SharedSsoAdminState) -> Self {
154        Self {
155            state,
156            snapshot_store: None,
157            snapshot_lock: Arc::new(AsyncMutex::new(())),
158        }
159    }
160
161    pub fn with_snapshot_store(mut self, store: Arc<dyn SnapshotStore>) -> Self {
162        self.snapshot_store = Some(store);
163        self
164    }
165
166    async fn save(&self) {
167        save_snapshot(
168            &self.state,
169            self.snapshot_store.clone(),
170            &self.snapshot_lock,
171        )
172        .await;
173    }
174}
175
176#[async_trait]
177impl AwsService for SsoAdminService {
178    fn service_name(&self) -> &str {
179        "sso"
180    }
181
182    async fn handle(&self, request: AwsRequest) -> Result<AwsResponse, AwsServiceError> {
183        let mutates = is_mutating(request.action.as_str());
184        let result = dispatch(self, &request);
185        if mutates && matches!(result.as_ref(), Ok(resp) if resp.status.is_success()) {
186            self.save().await;
187        }
188        result
189    }
190
191    fn supported_actions(&self) -> &[&str] {
192        SSOADMIN_ACTIONS
193    }
194}
195
196fn is_mutating(action: &str) -> bool {
197    const PREFIXES: &[&str] = &[
198        "Create",
199        "Update",
200        "Delete",
201        "Put",
202        "Attach",
203        "Detach",
204        "Add",
205        "Remove",
206        "Tag",
207        "Untag",
208        "Provision",
209    ];
210    PREFIXES.iter().any(|p| action.starts_with(p))
211}
212
213fn dispatch(s: &SsoAdminService, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
214    match req.action.as_str() {
215        // instances
216        "CreateInstance" => s.create_instance(req),
217        "DescribeInstance" => s.describe_instance(req),
218        "UpdateInstance" => s.update_instance(req),
219        "DeleteInstance" => s.delete_instance(req),
220        "ListInstances" => s.list_instances(req),
221        // permission sets
222        "CreatePermissionSet" => s.create_permission_set(req),
223        "DescribePermissionSet" => s.describe_permission_set(req),
224        "UpdatePermissionSet" => s.update_permission_set(req),
225        "DeletePermissionSet" => s.delete_permission_set(req),
226        "ListPermissionSets" => s.list_permission_sets(req),
227        // permission set policies
228        "AttachManagedPolicyToPermissionSet" => s.attach_managed_policy(req),
229        "DetachManagedPolicyFromPermissionSet" => s.detach_managed_policy(req),
230        "ListManagedPoliciesInPermissionSet" => s.list_managed_policies(req),
231        "AttachCustomerManagedPolicyReferenceToPermissionSet" => s.attach_customer_policy(req),
232        "DetachCustomerManagedPolicyReferenceFromPermissionSet" => s.detach_customer_policy(req),
233        "ListCustomerManagedPolicyReferencesInPermissionSet" => s.list_customer_policies(req),
234        "PutInlinePolicyToPermissionSet" => s.put_inline_policy(req),
235        "GetInlinePolicyForPermissionSet" => s.get_inline_policy(req),
236        "DeleteInlinePolicyFromPermissionSet" => s.delete_inline_policy(req),
237        "PutPermissionsBoundaryToPermissionSet" => s.put_permissions_boundary(req),
238        "GetPermissionsBoundaryForPermissionSet" => s.get_permissions_boundary(req),
239        "DeletePermissionsBoundaryFromPermissionSet" => s.delete_permissions_boundary(req),
240        // account assignments
241        "CreateAccountAssignment" => s.create_account_assignment(req),
242        "DeleteAccountAssignment" => s.delete_account_assignment(req),
243        "DescribeAccountAssignmentCreationStatus" => s.describe_aa_creation_status(req),
244        "DescribeAccountAssignmentDeletionStatus" => s.describe_aa_deletion_status(req),
245        "ListAccountAssignmentCreationStatus" => s.list_aa_creation_status(req),
246        "ListAccountAssignmentDeletionStatus" => s.list_aa_deletion_status(req),
247        "ListAccountAssignments" => s.list_account_assignments(req),
248        "ListAccountAssignmentsForPrincipal" => s.list_account_assignments_for_principal(req),
249        "ListAccountsForProvisionedPermissionSet" => s.list_accounts_for_provisioned(req),
250        "ListPermissionSetsProvisionedToAccount" => s.list_ps_provisioned_to_account(req),
251        // provisioning
252        "ProvisionPermissionSet" => s.provision_permission_set(req),
253        "DescribePermissionSetProvisioningStatus" => s.describe_ps_provisioning_status(req),
254        "ListPermissionSetProvisioningStatus" => s.list_ps_provisioning_status(req),
255        // applications
256        "CreateApplication" => s.create_application(req),
257        "DescribeApplication" => s.describe_application(req),
258        "UpdateApplication" => s.update_application(req),
259        "DeleteApplication" => s.delete_application(req),
260        "ListApplications" => s.list_applications(req),
261        // application assignments
262        "CreateApplicationAssignment" => s.create_application_assignment(req),
263        "DeleteApplicationAssignment" => s.delete_application_assignment(req),
264        "DescribeApplicationAssignment" => s.describe_application_assignment(req),
265        "ListApplicationAssignments" => s.list_application_assignments(req),
266        "ListApplicationAssignmentsForPrincipal" => {
267            s.list_application_assignments_for_principal(req)
268        }
269        "GetApplicationAssignmentConfiguration" => s.get_application_assignment_configuration(req),
270        "PutApplicationAssignmentConfiguration" => s.put_application_assignment_configuration(req),
271        // application access scopes
272        "PutApplicationAccessScope" => s.put_application_access_scope(req),
273        "GetApplicationAccessScope" => s.get_application_access_scope(req),
274        "DeleteApplicationAccessScope" => s.delete_application_access_scope(req),
275        "ListApplicationAccessScopes" => s.list_application_access_scopes(req),
276        // application authentication methods
277        "PutApplicationAuthenticationMethod" => s.put_application_authentication_method(req),
278        "GetApplicationAuthenticationMethod" => s.get_application_authentication_method(req),
279        "DeleteApplicationAuthenticationMethod" => s.delete_application_authentication_method(req),
280        "ListApplicationAuthenticationMethods" => s.list_application_authentication_methods(req),
281        // application grants
282        "PutApplicationGrant" => s.put_application_grant(req),
283        "GetApplicationGrant" => s.get_application_grant(req),
284        "DeleteApplicationGrant" => s.delete_application_grant(req),
285        "ListApplicationGrants" => s.list_application_grants(req),
286        // application session config
287        "GetApplicationSessionConfiguration" => s.get_application_session_configuration(req),
288        "PutApplicationSessionConfiguration" => s.put_application_session_configuration(req),
289        // application providers
290        "ListApplicationProviders" => s.list_application_providers(req),
291        "DescribeApplicationProvider" => s.describe_application_provider(req),
292        // trusted token issuers
293        "CreateTrustedTokenIssuer" => s.create_trusted_token_issuer(req),
294        "DescribeTrustedTokenIssuer" => s.describe_trusted_token_issuer(req),
295        "UpdateTrustedTokenIssuer" => s.update_trusted_token_issuer(req),
296        "DeleteTrustedTokenIssuer" => s.delete_trusted_token_issuer(req),
297        "ListTrustedTokenIssuers" => s.list_trusted_token_issuers(req),
298        // regions
299        "AddRegion" => s.add_region(req),
300        "RemoveRegion" => s.remove_region(req),
301        "DescribeRegion" => s.describe_region(req),
302        "ListRegions" => s.list_regions(req),
303        // access control attribute configuration
304        "CreateInstanceAccessControlAttributeConfiguration" => s.create_attr_config(req),
305        "DescribeInstanceAccessControlAttributeConfiguration" => s.describe_attr_config(req),
306        "UpdateInstanceAccessControlAttributeConfiguration" => s.update_attr_config(req),
307        "DeleteInstanceAccessControlAttributeConfiguration" => s.delete_attr_config(req),
308        // tagging
309        "TagResource" => s.tag_resource(req),
310        "UntagResource" => s.untag_resource(req),
311        "ListTagsForResource" => s.list_tags_for_resource(req),
312        _ => Err(AwsServiceError::action_not_implemented(
313            s.service_name(),
314            &req.action,
315        )),
316    }
317}
318
319// ===== helpers =====
320
321fn ok(v: Value) -> Result<AwsResponse, AwsServiceError> {
322    Ok(AwsResponse::json_value(StatusCode::OK, v))
323}
324
325fn parse(req: &AwsRequest) -> Result<Value, AwsServiceError> {
326    if req.body.is_empty() {
327        return Ok(json!({}));
328    }
329    serde_json::from_slice(&req.body)
330        .map_err(|e| validation(&format!("Request body is malformed: {e}")))
331}
332
333fn validation(msg: &str) -> AwsServiceError {
334    AwsServiceError::aws_error(StatusCode::BAD_REQUEST, "ValidationException", msg)
335}
336
337fn not_found(msg: &str) -> AwsServiceError {
338    AwsServiceError::aws_error(StatusCode::NOT_FOUND, "ResourceNotFoundException", msg)
339}
340
341fn req_str<'a>(b: &'a Value, f: &str) -> Result<&'a str, AwsServiceError> {
342    b.get(f)
343        .and_then(Value::as_str)
344        .filter(|s| !s.is_empty())
345        .ok_or_else(|| validation(&format!("{f} must be specified.")))
346}
347
348/// Character-length bound check for a present string field, matching the
349/// Smithy `@length` constraint. Absent fields are the caller's concern.
350fn check_len(b: &Value, field: &str, min: usize, max: usize) -> Result<(), AwsServiceError> {
351    if let Some(s) = b.get(field).and_then(Value::as_str) {
352        let n = s.chars().count();
353        if n < min || n > max {
354            return Err(validation(&format!(
355                "{field} must have length between {min} and {max}, inclusive."
356            )));
357        }
358    }
359    Ok(())
360}
361
362/// Enum-membership check for a present string field.
363fn check_enum(b: &Value, field: &str, allowed: &[&str]) -> Result<(), AwsServiceError> {
364    if let Some(s) = b.get(field).and_then(Value::as_str) {
365        if !allowed.contains(&s) {
366            return Err(validation(&format!(
367                "{field} must be one of [{}].",
368                allowed.join(", ")
369            )));
370        }
371    }
372    Ok(())
373}
374
375/// `@range` check for a present integer field.
376fn check_range(b: &Value, field: &str, min: i64, max: i64) -> Result<(), AwsServiceError> {
377    if let Some(v) = b.get(field) {
378        let n = v.as_i64().unwrap_or(min - 1);
379        if n < min || n > max {
380            return Err(validation(&format!(
381                "{field} must be between {min} and {max}, inclusive."
382            )));
383        }
384    }
385    Ok(())
386}
387
388/// Validate every input field whose constraints are consistent across all
389/// operations that carry it. Per-operation fields (Name, Description) are
390/// validated by the individual handlers.
391fn validate_common(b: &Value) -> Result<(), AwsServiceError> {
392    check_len(b, "InstanceArn", 10, 1224)?;
393    check_len(b, "PermissionSetArn", 10, 1224)?;
394    check_len(b, "ApplicationArn", 10, 1224)?;
395    check_len(b, "ApplicationProviderArn", 10, 1224)?;
396    check_len(b, "TrustedTokenIssuerArn", 10, 1224)?;
397    check_len(b, "ResourceArn", 10, 2048)?;
398    check_len(b, "ManagedPolicyArn", 20, 2048)?;
399    check_len(b, "PrincipalId", 1, 47)?;
400    check_len(b, "TargetId", 12, 12)?;
401    check_len(b, "AccountId", 12, 12)?;
402    check_len(b, "RegionName", 1, 32)?;
403    check_len(b, "NextToken", 0, 2048)?;
404    check_len(b, "ClientToken", 1, 64)?;
405    check_len(b, "RelayState", 1, 240)?;
406    check_len(b, "SessionDuration", 1, 100)?;
407    check_len(b, "InlinePolicy", 1, 32768)?;
408    check_len(b, "AccountAssignmentCreationRequestId", 36, 36)?;
409    check_len(b, "AccountAssignmentDeletionRequestId", 36, 36)?;
410    check_len(b, "ProvisionPermissionSetRequestId", 36, 36)?;
411    check_range(b, "MaxResults", 1, 100)?;
412    check_enum(b, "PrincipalType", &["USER", "GROUP"])?;
413    check_enum(
414        b,
415        "TargetType",
416        &["AWS_ACCOUNT", "ALL_PROVISIONED_ACCOUNTS"],
417    )?;
418    check_enum(
419        b,
420        "GrantType",
421        &[
422            "AUTHORIZATION_CODE",
423            "REFRESH_TOKEN",
424            "JWT_BEARER",
425            "TOKEN_EXCHANGE",
426        ],
427    )?;
428    check_enum(b, "AuthenticationMethodType", &["IAM"])?;
429    check_enum(
430        b,
431        "ProvisioningStatus",
432        &[
433            "LATEST_PERMISSION_SET_PROVISIONED",
434            "LATEST_PERMISSION_SET_NOT_PROVISIONED",
435        ],
436    )?;
437    check_enum(b, "Status", &["ENABLED", "DISABLED"])?;
438    check_enum(b, "TrustedTokenIssuerType", &["OIDC_JWT"])?;
439    check_enum(
440        b,
441        "UserBackgroundSessionApplicationStatus",
442        &["ENABLED", "DISABLED"],
443    )?;
444    Ok(())
445}
446
447fn now() -> i64 {
448    chrono::Utc::now().timestamp()
449}
450
451fn hex16() -> String {
452    Uuid::new_v4().simple().to_string()[..16].to_string()
453}
454
455fn hex10() -> String {
456    Uuid::new_v4().simple().to_string()[..10].to_string()
457}
458
459/// Validate pagination inputs, then window an ordered slice of result rows.
460fn paginate(rows: Vec<Value>, b: &Value) -> (Vec<Value>, Option<String>) {
461    let start = b
462        .get("NextToken")
463        .and_then(Value::as_str)
464        .and_then(|t| t.parse::<usize>().ok())
465        .unwrap_or(0);
466    let max = b
467        .get("MaxResults")
468        .and_then(Value::as_u64)
469        .map(|m| m.clamp(1, 100) as usize)
470        .unwrap_or(100);
471    let end = (start + max).min(rows.len());
472    let page = rows.get(start..end).unwrap_or(&[]).to_vec();
473    let next = if end < rows.len() {
474        Some(end.to_string())
475    } else {
476        None
477    };
478    (page, next)
479}
480
481/// Build a paginated list response body from ordered rows.
482fn list_response(key: &str, rows: Vec<Value>, b: &Value) -> Result<AwsResponse, AwsServiceError> {
483    let (page, next) = paginate(rows, b);
484    let mut out = json!({ key: page });
485    if let Some(t) = next {
486        out["NextToken"] = json!(t);
487    }
488    ok(out)
489}
490
491fn opt_str(m: &mut serde_json::Map<String, Value>, key: &str, v: &Option<String>) {
492    if let Some(val) = v {
493        m.insert(key.to_string(), json!(val));
494    }
495}
496
497fn build_permission_set(ps: &StoredPermissionSet) -> Value {
498    let mut m = serde_json::Map::new();
499    m.insert("PermissionSetArn".into(), json!(ps.arn));
500    m.insert("Name".into(), json!(ps.name));
501    m.insert("CreatedDate".into(), json!(ps.created_date));
502    opt_str(&mut m, "Description", &ps.description);
503    opt_str(&mut m, "SessionDuration", &ps.session_duration);
504    opt_str(&mut m, "RelayState", &ps.relay_state);
505    Value::Object(m)
506}
507
508fn build_region(r: &StoredRegion) -> Value {
509    json!({
510        "RegionName": r.region_name,
511        "Status": r.status,
512        "AddedDate": r.added_date,
513        "IsPrimaryRegion": r.is_primary,
514    })
515}
516
517fn build_instance_describe(inst: &StoredInstance) -> Value {
518    let mut m = serde_json::Map::new();
519    m.insert("InstanceArn".into(), json!(inst.arn));
520    m.insert("IdentityStoreId".into(), json!(inst.identity_store_id));
521    m.insert("OwnerAccountId".into(), json!(inst.owner_account_id));
522    m.insert("CreatedDate".into(), json!(inst.created_date));
523    m.insert("Status".into(), json!(inst.status));
524    opt_str(&mut m, "Name", &inst.name);
525    Value::Object(m)
526}
527
528fn build_instance_metadata(inst: &StoredInstance) -> Value {
529    let mut m = serde_json::Map::new();
530    m.insert("InstanceArn".into(), json!(inst.arn));
531    m.insert("IdentityStoreId".into(), json!(inst.identity_store_id));
532    m.insert("OwnerAccountId".into(), json!(inst.owner_account_id));
533    m.insert("CreatedDate".into(), json!(inst.created_date));
534    m.insert("Status".into(), json!(inst.status));
535    opt_str(&mut m, "Name", &inst.name);
536    opt_str(&mut m, "PrimaryRegion", &inst.primary_region);
537    let regions: Vec<Value> = inst.regions.iter().map(build_region).collect();
538    m.insert("Regions".into(), json!(regions));
539    Value::Object(m)
540}
541
542fn build_application(app: &StoredApplication) -> Value {
543    let mut m = serde_json::Map::new();
544    m.insert("ApplicationArn".into(), json!(app.arn));
545    m.insert("ApplicationProviderArn".into(), json!(app.provider_arn));
546    m.insert("ApplicationAccount".into(), json!(app.account));
547    m.insert("InstanceArn".into(), json!(app.instance_arn));
548    m.insert("IdentityStoreArn".into(), json!(app.identity_store_arn));
549    m.insert("CreatedDate".into(), json!(app.created_date));
550    opt_str(&mut m, "Name", &app.name);
551    opt_str(&mut m, "Status", &app.status);
552    opt_str(&mut m, "Description", &app.description);
553    opt_str(&mut m, "CreatedFrom", &app.created_from);
554    if let Some(po) = &app.portal_options {
555        m.insert("PortalOptions".into(), po.clone());
556    }
557    Value::Object(m)
558}
559
560fn build_tti_describe(tti: &StoredTrustedTokenIssuer) -> Value {
561    let mut m = serde_json::Map::new();
562    m.insert("TrustedTokenIssuerArn".into(), json!(tti.arn));
563    m.insert("TrustedTokenIssuerType".into(), json!(tti.issuer_type));
564    opt_str(&mut m, "Name", &tti.name);
565    if let Some(cfg) = &tti.config {
566        m.insert("TrustedTokenIssuerConfiguration".into(), cfg.clone());
567    }
568    Value::Object(m)
569}
570
571fn build_tti_metadata(tti: &StoredTrustedTokenIssuer) -> Value {
572    let mut m = serde_json::Map::new();
573    m.insert("TrustedTokenIssuerArn".into(), json!(tti.arn));
574    m.insert("TrustedTokenIssuerType".into(), json!(tti.issuer_type));
575    opt_str(&mut m, "Name", &tti.name);
576    Value::Object(m)
577}
578
579fn build_op_status(st: &StoredOpStatus) -> Value {
580    json!({
581        "Status": st.status,
582        "RequestId": st.request_id,
583        "TargetId": st.target_id,
584        "TargetType": st.target_type,
585        "PermissionSetArn": st.permission_set_arn,
586        "PrincipalType": st.principal_type,
587        "PrincipalId": st.principal_id,
588        "CreatedDate": st.created_date,
589    })
590}
591
592fn build_prov_status(st: &StoredProvisioningStatus) -> Value {
593    let mut m = serde_json::Map::new();
594    m.insert("Status".into(), json!(st.status));
595    m.insert("RequestId".into(), json!(st.request_id));
596    m.insert("PermissionSetArn".into(), json!(st.permission_set_arn));
597    m.insert("CreatedDate".into(), json!(st.created_date));
598    if let Some(acct) = &st.account_id {
599        m.insert("AccountId".into(), json!(acct));
600    }
601    Value::Object(m)
602}
603
604/// Persist the create-time `Tags` list of a resource into the tag store,
605/// keyed by its ARN, so `ListTagsForResource` round-trips them.
606fn store_tags(acct: &mut crate::state::SsoAdminData, arn: &str, b: &Value) {
607    let Some(tags) = b.get("Tags").and_then(Value::as_array) else {
608        return;
609    };
610    if tags.is_empty() {
611        return;
612    }
613    let entry = acct.tags.entry(arn.to_string()).or_default();
614    for tag in tags {
615        if let (Some(k), Some(v)) = (
616            tag.get("Key").and_then(Value::as_str),
617            tag.get("Value").and_then(Value::as_str),
618        ) {
619            entry.insert(k.to_string(), v.to_string());
620        }
621    }
622}
623
624/// Ensure a stored `PortalOptions` echoes AWS's server-side defaults —
625/// `Visibility` defaults to `ENABLED` when the caller omits it, and the
626/// describe response must surface that default.
627fn portal_options_with_defaults(po: Option<Value>) -> Option<Value> {
628    po.map(|mut v| {
629        if let Value::Object(m) = &mut v {
630            m.entry("Visibility").or_insert(json!("ENABLED"));
631        }
632        v
633    })
634}
635
636/// Recursively merge `patch` into `base`: object members are merged key by
637/// key; every other node is overwritten. Used so `UpdateTrustedTokenIssuer`
638/// (whose update configuration omits the immutable `IssuerUrl`) keeps the
639/// fields it doesn't restate instead of dropping them.
640fn deep_merge(base: &mut Value, patch: &Value) {
641    match (base, patch) {
642        (Value::Object(b), Value::Object(p)) => {
643            for (k, v) in p {
644                deep_merge(b.entry(k.clone()).or_insert(Value::Null), v);
645            }
646        }
647        (b, p) => *b = p.clone(),
648    }
649}
650
651impl SsoAdminService {
652    // ---------- instances ----------
653
654    fn create_instance(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
655        let b = parse(req)?;
656        validate_common(&b)?;
657        check_len(&b, "Name", 0, 255)?;
658        let ssoins = format!("ssoins-{}", hex16());
659        let arn = format!("arn:aws:sso:::instance/{ssoins}");
660        let identity_store_id = format!("d-{}", hex10());
661        let inst = StoredInstance {
662            arn: arn.clone(),
663            ssoins,
664            identity_store_id,
665            owner_account_id: req.account_id.clone(),
666            name: b.get("Name").and_then(Value::as_str).map(str::to_string),
667            status: "ACTIVE".into(),
668            created_date: now(),
669            regions: Vec::new(),
670            primary_region: Some(req.region.clone()),
671            attr_config: None,
672            attr_config_status: None,
673        };
674        {
675            let mut guard = self.state.write();
676            let acct = guard.get_or_create(&req.account_id);
677            acct.instances.insert(arn.clone(), inst);
678            store_tags(acct, &arn, &b);
679        }
680        ok(json!({ "InstanceArn": arn }))
681    }
682
683    fn describe_instance(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
684        let b = parse(req)?;
685        validate_common(&b)?;
686        let arn = req_str(&b, "InstanceArn")?.to_string();
687        let guard = self.state.read();
688        if let Some(inst) = guard
689            .get(&req.account_id)
690            .and_then(|a| a.instances.get(&arn))
691        {
692            return ok(build_instance_describe(inst));
693        }
694        // `DescribeInstance` declares no ResourceNotFoundException, so a valid
695        // instance ARN that we don't have on record is surfaced as an
696        // auto-provisioned ACTIVE instance rather than an error.
697        let ssoins = arn.rsplit('/').next().unwrap_or("ssoins-unknown");
698        let synth = StoredInstance {
699            arn: arn.clone(),
700            ssoins: ssoins.to_string(),
701            identity_store_id: format!("d-{}", hex10()),
702            owner_account_id: req.account_id.clone(),
703            name: None,
704            status: "ACTIVE".into(),
705            created_date: now(),
706            regions: Vec::new(),
707            primary_region: Some(req.region.clone()),
708            attr_config: None,
709            attr_config_status: None,
710        };
711        ok(build_instance_describe(&synth))
712    }
713
714    fn update_instance(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
715        let b = parse(req)?;
716        validate_common(&b)?;
717        check_len(&b, "Name", 0, 255)?;
718        let arn = req_str(&b, "InstanceArn")?.to_string();
719        let mut guard = self.state.write();
720        let inst = guard
721            .get_mut(&req.account_id)
722            .and_then(|a| a.instances.get_mut(&arn))
723            .ok_or_else(|| not_found("Instance not found."))?;
724        if let Some(name) = b.get("Name").and_then(Value::as_str) {
725            inst.name = Some(name.to_string());
726        }
727        ok(json!({}))
728    }
729
730    fn delete_instance(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
731        let b = parse(req)?;
732        validate_common(&b)?;
733        let arn = req_str(&b, "InstanceArn")?.to_string();
734        if let Some(a) = self.state.write().get_mut(&req.account_id) {
735            a.instances.remove(&arn);
736        }
737        ok(json!({}))
738    }
739
740    fn list_instances(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
741        let b = parse(req)?;
742        validate_common(&b)?;
743        let guard = self.state.read();
744        let rows: Vec<Value> = guard
745            .get(&req.account_id)
746            .map(|a| a.instances.values().map(build_instance_metadata).collect())
747            .unwrap_or_default();
748        list_response("Instances", rows, &b)
749    }
750
751    // ---------- permission sets ----------
752
753    fn permission_set<'a>(
754        &self,
755        guard: &'a fakecloud_core::multi_account::MultiAccountState<crate::state::SsoAdminData>,
756        account: &str,
757        arn: &str,
758    ) -> Option<&'a StoredPermissionSet> {
759        guard.get(account).and_then(|a| a.permission_sets.get(arn))
760    }
761
762    fn create_permission_set(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
763        let b = parse(req)?;
764        validate_common(&b)?;
765        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
766        let name = req_str(&b, "Name")?.to_string();
767        check_len(&b, "Name", 1, 32)?;
768        check_len(&b, "Description", 1, 700)?;
769        let mut guard = self.state.write();
770        let acct = guard.get_or_create(&req.account_id);
771        let inst = acct
772            .instances
773            .get(&instance_arn)
774            .ok_or_else(|| not_found("Instance not found."))?;
775        let ssoins = inst.ssoins.clone();
776        let arn = format!("arn:aws:sso:::permissionSet/{ssoins}/ps-{}", hex16());
777        let ps = StoredPermissionSet {
778            arn: arn.clone(),
779            instance_arn,
780            name,
781            description: b
782                .get("Description")
783                .and_then(Value::as_str)
784                .map(str::to_string),
785            session_duration: b
786                .get("SessionDuration")
787                .and_then(Value::as_str)
788                .map(str::to_string),
789            relay_state: b
790                .get("RelayState")
791                .and_then(Value::as_str)
792                .map(str::to_string),
793            created_date: now(),
794            inline_policy: None,
795            permissions_boundary: None,
796            managed_policies: Vec::new(),
797            customer_managed: Vec::new(),
798        };
799        acct.permission_sets.insert(arn.clone(), ps.clone());
800        store_tags(acct, &arn, &b);
801        ok(json!({ "PermissionSet": build_permission_set(&ps) }))
802    }
803
804    fn describe_permission_set(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
805        let b = parse(req)?;
806        validate_common(&b)?;
807        req_str(&b, "InstanceArn")?;
808        let arn = req_str(&b, "PermissionSetArn")?.to_string();
809        let guard = self.state.read();
810        let ps = self
811            .permission_set(&guard, &req.account_id, &arn)
812            .ok_or_else(|| not_found("Permission set not found."))?;
813        ok(json!({ "PermissionSet": build_permission_set(ps) }))
814    }
815
816    fn update_permission_set(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
817        let b = parse(req)?;
818        validate_common(&b)?;
819        check_len(&b, "Description", 1, 700)?;
820        req_str(&b, "InstanceArn")?;
821        let arn = req_str(&b, "PermissionSetArn")?.to_string();
822        let mut guard = self.state.write();
823        let ps = guard
824            .get_mut(&req.account_id)
825            .and_then(|a| a.permission_sets.get_mut(&arn))
826            .ok_or_else(|| not_found("Permission set not found."))?;
827        if let Some(d) = b.get("Description").and_then(Value::as_str) {
828            ps.description = Some(d.to_string());
829        }
830        if let Some(d) = b.get("SessionDuration").and_then(Value::as_str) {
831            ps.session_duration = Some(d.to_string());
832        }
833        if let Some(d) = b.get("RelayState").and_then(Value::as_str) {
834            ps.relay_state = Some(d.to_string());
835        }
836        ok(json!({}))
837    }
838
839    fn delete_permission_set(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
840        let b = parse(req)?;
841        validate_common(&b)?;
842        req_str(&b, "InstanceArn")?;
843        let arn = req_str(&b, "PermissionSetArn")?.to_string();
844        if let Some(a) = self.state.write().get_mut(&req.account_id) {
845            a.permission_sets.remove(&arn);
846        }
847        ok(json!({}))
848    }
849
850    fn list_permission_sets(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
851        let b = parse(req)?;
852        validate_common(&b)?;
853        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
854        let guard = self.state.read();
855        let rows: Vec<Value> = guard
856            .get(&req.account_id)
857            .map(|a| {
858                a.permission_sets
859                    .values()
860                    .filter(|ps| ps.instance_arn == instance_arn)
861                    .map(|ps| json!(ps.arn))
862                    .collect()
863            })
864            .unwrap_or_default();
865        list_response("PermissionSets", rows, &b)
866    }
867
868    // ---------- permission set policies ----------
869
870    fn with_permission_set<F>(
871        &self,
872        req: &AwsRequest,
873        b: &Value,
874        f: F,
875    ) -> Result<AwsResponse, AwsServiceError>
876    where
877        F: FnOnce(&mut StoredPermissionSet) -> Result<AwsResponse, AwsServiceError>,
878    {
879        let arn = req_str(b, "PermissionSetArn")?.to_string();
880        let mut guard = self.state.write();
881        let ps = guard
882            .get_mut(&req.account_id)
883            .and_then(|a| a.permission_sets.get_mut(&arn))
884            .ok_or_else(|| not_found("Permission set not found."))?;
885        f(ps)
886    }
887
888    fn attach_managed_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
889        let b = parse(req)?;
890        validate_common(&b)?;
891        req_str(&b, "InstanceArn")?;
892        let policy_arn = req_str(&b, "ManagedPolicyArn")?.to_string();
893        let name = policy_arn
894            .rsplit('/')
895            .next()
896            .unwrap_or(&policy_arn)
897            .to_string();
898        self.with_permission_set(req, &b, |ps| {
899            if !ps.managed_policies.iter().any(|p| p.arn == policy_arn) {
900                ps.managed_policies.push(StoredManagedPolicy {
901                    name,
902                    arn: policy_arn,
903                });
904            }
905            ok(json!({}))
906        })
907    }
908
909    fn detach_managed_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
910        let b = parse(req)?;
911        validate_common(&b)?;
912        req_str(&b, "InstanceArn")?;
913        let policy_arn = req_str(&b, "ManagedPolicyArn")?.to_string();
914        self.with_permission_set(req, &b, |ps| {
915            ps.managed_policies.retain(|p| p.arn != policy_arn);
916            ok(json!({}))
917        })
918    }
919
920    fn list_managed_policies(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
921        let b = parse(req)?;
922        validate_common(&b)?;
923        req_str(&b, "InstanceArn")?;
924        let arn = req_str(&b, "PermissionSetArn")?.to_string();
925        let guard = self.state.read();
926        let rows: Vec<Value> = self
927            .permission_set(&guard, &req.account_id, &arn)
928            .map(|ps| {
929                ps.managed_policies
930                    .iter()
931                    .map(|p| json!({ "Name": p.name, "Arn": p.arn }))
932                    .collect()
933            })
934            .unwrap_or_default();
935        list_response("AttachedManagedPolicies", rows, &b)
936    }
937
938    fn attach_customer_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
939        let b = parse(req)?;
940        validate_common(&b)?;
941        req_str(&b, "InstanceArn")?;
942        let reference = b
943            .get("CustomerManagedPolicyReference")
944            .cloned()
945            .ok_or_else(|| validation("CustomerManagedPolicyReference must be specified."))?;
946        let ref_name = reference
947            .get("Name")
948            .and_then(Value::as_str)
949            .map(str::to_string);
950        self.with_permission_set(req, &b, |ps| {
951            let exists = ps
952                .customer_managed
953                .iter()
954                .any(|r| r.get("Name").and_then(Value::as_str).map(str::to_string) == ref_name);
955            if !exists {
956                ps.customer_managed.push(reference);
957            }
958            ok(json!({}))
959        })
960    }
961
962    fn detach_customer_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
963        let b = parse(req)?;
964        validate_common(&b)?;
965        req_str(&b, "InstanceArn")?;
966        let reference = b
967            .get("CustomerManagedPolicyReference")
968            .cloned()
969            .ok_or_else(|| validation("CustomerManagedPolicyReference must be specified."))?;
970        let ref_name = reference
971            .get("Name")
972            .and_then(Value::as_str)
973            .map(str::to_string);
974        self.with_permission_set(req, &b, |ps| {
975            ps.customer_managed
976                .retain(|r| r.get("Name").and_then(Value::as_str).map(str::to_string) != ref_name);
977            ok(json!({}))
978        })
979    }
980
981    fn list_customer_policies(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
982        let b = parse(req)?;
983        validate_common(&b)?;
984        req_str(&b, "InstanceArn")?;
985        let arn = req_str(&b, "PermissionSetArn")?.to_string();
986        let guard = self.state.read();
987        let rows: Vec<Value> = self
988            .permission_set(&guard, &req.account_id, &arn)
989            .map(|ps| ps.customer_managed.clone())
990            .unwrap_or_default();
991        list_response("CustomerManagedPolicyReferences", rows, &b)
992    }
993
994    fn put_inline_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
995        let b = parse(req)?;
996        validate_common(&b)?;
997        req_str(&b, "InstanceArn")?;
998        let policy = req_str(&b, "InlinePolicy")?.to_string();
999        self.with_permission_set(req, &b, |ps| {
1000            ps.inline_policy = Some(policy);
1001            ok(json!({}))
1002        })
1003    }
1004
1005    fn get_inline_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1006        let b = parse(req)?;
1007        validate_common(&b)?;
1008        req_str(&b, "InstanceArn")?;
1009        let arn = req_str(&b, "PermissionSetArn")?.to_string();
1010        let guard = self.state.read();
1011        let ps = self
1012            .permission_set(&guard, &req.account_id, &arn)
1013            .ok_or_else(|| not_found("Permission set not found."))?;
1014        // An unset inline policy resolves to the empty string, matching AWS.
1015        ok(json!({ "InlinePolicy": ps.inline_policy.clone().unwrap_or_default() }))
1016    }
1017
1018    fn delete_inline_policy(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1019        let b = parse(req)?;
1020        validate_common(&b)?;
1021        req_str(&b, "InstanceArn")?;
1022        self.with_permission_set(req, &b, |ps| {
1023            ps.inline_policy = None;
1024            ok(json!({}))
1025        })
1026    }
1027
1028    fn put_permissions_boundary(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1029        let b = parse(req)?;
1030        validate_common(&b)?;
1031        req_str(&b, "InstanceArn")?;
1032        let boundary = b
1033            .get("PermissionsBoundary")
1034            .cloned()
1035            .ok_or_else(|| validation("PermissionsBoundary must be specified."))?;
1036        // A permissions boundary is exactly one of a managed policy ARN or a
1037        // customer-managed policy reference, never both.
1038        if boundary.get("ManagedPolicyArn").is_some()
1039            && boundary.get("CustomerManagedPolicyReference").is_some()
1040        {
1041            return Err(validation(
1042                "Only ManagedPolicyArn or CustomerManagedPolicyReference should be given.",
1043            ));
1044        }
1045        self.with_permission_set(req, &b, |ps| {
1046            ps.permissions_boundary = Some(boundary);
1047            ok(json!({}))
1048        })
1049    }
1050
1051    fn get_permissions_boundary(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1052        let b = parse(req)?;
1053        validate_common(&b)?;
1054        req_str(&b, "InstanceArn")?;
1055        let arn = req_str(&b, "PermissionSetArn")?.to_string();
1056        let guard = self.state.read();
1057        let ps = self
1058            .permission_set(&guard, &req.account_id, &arn)
1059            .ok_or_else(|| not_found("Permission set not found."))?;
1060        let boundary = ps
1061            .permissions_boundary
1062            .clone()
1063            .ok_or_else(|| not_found("No permissions boundary is set."))?;
1064        ok(json!({ "PermissionsBoundary": boundary }))
1065    }
1066
1067    fn delete_permissions_boundary(
1068        &self,
1069        req: &AwsRequest,
1070    ) -> Result<AwsResponse, AwsServiceError> {
1071        let b = parse(req)?;
1072        validate_common(&b)?;
1073        req_str(&b, "InstanceArn")?;
1074        self.with_permission_set(req, &b, |ps| {
1075            ps.permissions_boundary = None;
1076            ok(json!({}))
1077        })
1078    }
1079
1080    // ---------- account assignments ----------
1081
1082    fn create_account_assignment(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1083        let b = parse(req)?;
1084        validate_common(&b)?;
1085        req_str(&b, "InstanceArn")?;
1086        let target_id = req_str(&b, "TargetId")?.to_string();
1087        req_str(&b, "TargetType")?;
1088        let ps_arn = req_str(&b, "PermissionSetArn")?.to_string();
1089        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1090        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1091        let request_id = Uuid::new_v4().to_string();
1092        let st = StoredOpStatus {
1093            request_id: request_id.clone(),
1094            status: "IN_PROGRESS".into(),
1095            target_id: target_id.clone(),
1096            target_type: b
1097                .get("TargetType")
1098                .and_then(Value::as_str)
1099                .unwrap_or("AWS_ACCOUNT")
1100                .to_string(),
1101            permission_set_arn: ps_arn.clone(),
1102            principal_type: principal_type.clone(),
1103            principal_id: principal_id.clone(),
1104            created_date: now(),
1105        };
1106        let mut guard = self.state.write();
1107        let acct = guard.get_or_create(&req.account_id);
1108        // Record the actual assignment (no duplicate) plus the async status.
1109        if !acct.assignments.iter().any(|a| {
1110            a.account_id == target_id
1111                && a.permission_set_arn == ps_arn
1112                && a.principal_id == principal_id
1113        }) {
1114            acct.assignments.push(StoredAssignment {
1115                account_id: target_id,
1116                permission_set_arn: ps_arn,
1117                principal_type,
1118                principal_id,
1119            });
1120        }
1121        acct.aa_create_status.insert(request_id, st.clone());
1122        ok(json!({ "AccountAssignmentCreationStatus": build_op_status(&st) }))
1123    }
1124
1125    fn delete_account_assignment(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1126        let b = parse(req)?;
1127        validate_common(&b)?;
1128        req_str(&b, "InstanceArn")?;
1129        let target_id = req_str(&b, "TargetId")?.to_string();
1130        req_str(&b, "TargetType")?;
1131        let ps_arn = req_str(&b, "PermissionSetArn")?.to_string();
1132        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1133        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1134        let request_id = Uuid::new_v4().to_string();
1135        let st = StoredOpStatus {
1136            request_id: request_id.clone(),
1137            status: "IN_PROGRESS".into(),
1138            target_id: target_id.clone(),
1139            target_type: b
1140                .get("TargetType")
1141                .and_then(Value::as_str)
1142                .unwrap_or("AWS_ACCOUNT")
1143                .to_string(),
1144            permission_set_arn: ps_arn.clone(),
1145            principal_type,
1146            principal_id: principal_id.clone(),
1147            created_date: now(),
1148        };
1149        let mut guard = self.state.write();
1150        let acct = guard.get_or_create(&req.account_id);
1151        acct.assignments.retain(|a| {
1152            !(a.account_id == target_id
1153                && a.permission_set_arn == ps_arn
1154                && a.principal_id == principal_id)
1155        });
1156        acct.aa_delete_status.insert(request_id, st.clone());
1157        ok(json!({ "AccountAssignmentDeletionStatus": build_op_status(&st) }))
1158    }
1159
1160    fn describe_aa_creation_status(
1161        &self,
1162        req: &AwsRequest,
1163    ) -> Result<AwsResponse, AwsServiceError> {
1164        let b = parse(req)?;
1165        validate_common(&b)?;
1166        req_str(&b, "InstanceArn")?;
1167        let id = req_str(&b, "AccountAssignmentCreationRequestId")?.to_string();
1168        let mut guard = self.state.write();
1169        let st = guard
1170            .get_mut(&req.account_id)
1171            .and_then(|a| a.aa_create_status.get_mut(&id))
1172            .ok_or_else(|| not_found("Assignment creation status not found."))?;
1173        st.status = "SUCCEEDED".into();
1174        ok(json!({ "AccountAssignmentCreationStatus": build_op_status(st) }))
1175    }
1176
1177    fn describe_aa_deletion_status(
1178        &self,
1179        req: &AwsRequest,
1180    ) -> Result<AwsResponse, AwsServiceError> {
1181        let b = parse(req)?;
1182        validate_common(&b)?;
1183        req_str(&b, "InstanceArn")?;
1184        let id = req_str(&b, "AccountAssignmentDeletionRequestId")?.to_string();
1185        let mut guard = self.state.write();
1186        let st = guard
1187            .get_mut(&req.account_id)
1188            .and_then(|a| a.aa_delete_status.get_mut(&id))
1189            .ok_or_else(|| not_found("Assignment deletion status not found."))?;
1190        st.status = "SUCCEEDED".into();
1191        ok(json!({ "AccountAssignmentDeletionStatus": build_op_status(st) }))
1192    }
1193
1194    fn list_aa_creation_status(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1195        let b = parse(req)?;
1196        validate_common(&b)?;
1197        req_str(&b, "InstanceArn")?;
1198        let guard = self.state.read();
1199        let rows: Vec<Value> = guard
1200            .get(&req.account_id)
1201            .map(|a| {
1202                a.aa_create_status
1203                    .values()
1204                    .map(|st| json!({ "Status": st.status, "RequestId": st.request_id, "CreatedDate": st.created_date }))
1205                    .collect()
1206            })
1207            .unwrap_or_default();
1208        list_response("AccountAssignmentsCreationStatus", rows, &b)
1209    }
1210
1211    fn list_aa_deletion_status(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1212        let b = parse(req)?;
1213        validate_common(&b)?;
1214        req_str(&b, "InstanceArn")?;
1215        let guard = self.state.read();
1216        let rows: Vec<Value> = guard
1217            .get(&req.account_id)
1218            .map(|a| {
1219                a.aa_delete_status
1220                    .values()
1221                    .map(|st| json!({ "Status": st.status, "RequestId": st.request_id, "CreatedDate": st.created_date }))
1222                    .collect()
1223            })
1224            .unwrap_or_default();
1225        list_response("AccountAssignmentsDeletionStatus", rows, &b)
1226    }
1227
1228    fn list_account_assignments(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1229        let b = parse(req)?;
1230        validate_common(&b)?;
1231        req_str(&b, "InstanceArn")?;
1232        let account_id = req_str(&b, "AccountId")?.to_string();
1233        let ps_arn = req_str(&b, "PermissionSetArn")?.to_string();
1234        let guard = self.state.read();
1235        let rows: Vec<Value> = guard
1236            .get(&req.account_id)
1237            .map(|a| {
1238                a.assignments
1239                    .iter()
1240                    .filter(|x| x.account_id == account_id && x.permission_set_arn == ps_arn)
1241                    .map(|x| {
1242                        json!({
1243                            "AccountId": x.account_id,
1244                            "PermissionSetArn": x.permission_set_arn,
1245                            "PrincipalType": x.principal_type,
1246                            "PrincipalId": x.principal_id,
1247                        })
1248                    })
1249                    .collect()
1250            })
1251            .unwrap_or_default();
1252        list_response("AccountAssignments", rows, &b)
1253    }
1254
1255    fn list_account_assignments_for_principal(
1256        &self,
1257        req: &AwsRequest,
1258    ) -> Result<AwsResponse, AwsServiceError> {
1259        let b = parse(req)?;
1260        validate_common(&b)?;
1261        req_str(&b, "InstanceArn")?;
1262        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1263        req_str(&b, "PrincipalType")?;
1264        let guard = self.state.read();
1265        let rows: Vec<Value> = guard
1266            .get(&req.account_id)
1267            .map(|a| {
1268                a.assignments
1269                    .iter()
1270                    .filter(|x| x.principal_id == principal_id)
1271                    .map(|x| {
1272                        json!({
1273                            "AccountId": x.account_id,
1274                            "PermissionSetArn": x.permission_set_arn,
1275                            "PrincipalId": x.principal_id,
1276                            "PrincipalType": x.principal_type,
1277                        })
1278                    })
1279                    .collect()
1280            })
1281            .unwrap_or_default();
1282        list_response("AccountAssignments", rows, &b)
1283    }
1284
1285    fn list_accounts_for_provisioned(
1286        &self,
1287        req: &AwsRequest,
1288    ) -> Result<AwsResponse, AwsServiceError> {
1289        let b = parse(req)?;
1290        validate_common(&b)?;
1291        req_str(&b, "InstanceArn")?;
1292        let ps_arn = req_str(&b, "PermissionSetArn")?.to_string();
1293        let guard = self.state.read();
1294        let mut accounts: Vec<String> = guard
1295            .get(&req.account_id)
1296            .map(|a| {
1297                a.assignments
1298                    .iter()
1299                    .filter(|x| x.permission_set_arn == ps_arn)
1300                    .map(|x| x.account_id.clone())
1301                    .collect()
1302            })
1303            .unwrap_or_default();
1304        accounts.sort();
1305        accounts.dedup();
1306        let rows: Vec<Value> = accounts.into_iter().map(|a| json!(a)).collect();
1307        list_response("AccountIds", rows, &b)
1308    }
1309
1310    fn list_ps_provisioned_to_account(
1311        &self,
1312        req: &AwsRequest,
1313    ) -> Result<AwsResponse, AwsServiceError> {
1314        let b = parse(req)?;
1315        validate_common(&b)?;
1316        req_str(&b, "InstanceArn")?;
1317        let account_id = req_str(&b, "AccountId")?.to_string();
1318        let guard = self.state.read();
1319        let mut ps: Vec<String> = guard
1320            .get(&req.account_id)
1321            .map(|a| {
1322                a.assignments
1323                    .iter()
1324                    .filter(|x| x.account_id == account_id)
1325                    .map(|x| x.permission_set_arn.clone())
1326                    .collect()
1327            })
1328            .unwrap_or_default();
1329        ps.sort();
1330        ps.dedup();
1331        let rows: Vec<Value> = ps.into_iter().map(|a| json!(a)).collect();
1332        list_response("PermissionSets", rows, &b)
1333    }
1334
1335    // ---------- provisioning ----------
1336
1337    fn provision_permission_set(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1338        let b = parse(req)?;
1339        validate_common(&b)?;
1340        req_str(&b, "InstanceArn")?;
1341        let ps_arn = req_str(&b, "PermissionSetArn")?.to_string();
1342        req_str(&b, "TargetType")?;
1343        let request_id = Uuid::new_v4().to_string();
1344        let st = StoredProvisioningStatus {
1345            request_id: request_id.clone(),
1346            status: "IN_PROGRESS".into(),
1347            account_id: b
1348                .get("TargetId")
1349                .and_then(Value::as_str)
1350                .map(str::to_string),
1351            permission_set_arn: ps_arn,
1352            created_date: now(),
1353        };
1354        self.state
1355            .write()
1356            .get_or_create(&req.account_id)
1357            .ps_provisioning
1358            .insert(request_id, st.clone());
1359        ok(json!({ "PermissionSetProvisioningStatus": build_prov_status(&st) }))
1360    }
1361
1362    fn describe_ps_provisioning_status(
1363        &self,
1364        req: &AwsRequest,
1365    ) -> Result<AwsResponse, AwsServiceError> {
1366        let b = parse(req)?;
1367        validate_common(&b)?;
1368        req_str(&b, "InstanceArn")?;
1369        let id = req_str(&b, "ProvisionPermissionSetRequestId")?.to_string();
1370        let mut guard = self.state.write();
1371        let st = guard
1372            .get_mut(&req.account_id)
1373            .and_then(|a| a.ps_provisioning.get_mut(&id))
1374            .ok_or_else(|| not_found("Provisioning status not found."))?;
1375        st.status = "SUCCEEDED".into();
1376        ok(json!({ "PermissionSetProvisioningStatus": build_prov_status(st) }))
1377    }
1378
1379    fn list_ps_provisioning_status(
1380        &self,
1381        req: &AwsRequest,
1382    ) -> Result<AwsResponse, AwsServiceError> {
1383        let b = parse(req)?;
1384        validate_common(&b)?;
1385        req_str(&b, "InstanceArn")?;
1386        let guard = self.state.read();
1387        let rows: Vec<Value> = guard
1388            .get(&req.account_id)
1389            .map(|a| {
1390                a.ps_provisioning
1391                    .values()
1392                    .map(|st| json!({ "Status": st.status, "RequestId": st.request_id, "CreatedDate": st.created_date }))
1393                    .collect()
1394            })
1395            .unwrap_or_default();
1396        list_response("PermissionSetsProvisioningStatus", rows, &b)
1397    }
1398
1399    // ---------- applications ----------
1400
1401    fn application<'a>(
1402        &self,
1403        guard: &'a fakecloud_core::multi_account::MultiAccountState<crate::state::SsoAdminData>,
1404        account: &str,
1405        arn: &str,
1406    ) -> Option<&'a StoredApplication> {
1407        guard.get(account).and_then(|a| a.applications.get(arn))
1408    }
1409
1410    fn create_application(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1411        let b = parse(req)?;
1412        validate_common(&b)?;
1413        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
1414        let provider_arn = req_str(&b, "ApplicationProviderArn")?.to_string();
1415        let name = req_str(&b, "Name")?.to_string();
1416        check_len(&b, "Name", 1, 100)?;
1417        check_len(&b, "Description", 1, 128)?;
1418        let mut guard = self.state.write();
1419        let acct = guard.get_or_create(&req.account_id);
1420        let inst = acct
1421            .instances
1422            .get(&instance_arn)
1423            .ok_or_else(|| not_found("Instance not found."))?;
1424        let ssoins = inst.ssoins.clone();
1425        let identity_store_id = inst.identity_store_id.clone();
1426        let arn = format!(
1427            "arn:aws:sso::{}:application/{ssoins}/apl-{}",
1428            req.account_id,
1429            hex16()
1430        );
1431        let identity_store_arn = format!(
1432            "arn:aws:identitystore::{}:identitystore/{identity_store_id}",
1433            req.account_id
1434        );
1435        let app = StoredApplication {
1436            arn: arn.clone(),
1437            provider_arn,
1438            name: Some(name),
1439            account: req.account_id.clone(),
1440            instance_arn: instance_arn.clone(),
1441            identity_store_arn: identity_store_arn.clone(),
1442            status: Some(
1443                b.get("Status")
1444                    .and_then(Value::as_str)
1445                    .unwrap_or("ENABLED")
1446                    .to_string(),
1447            ),
1448            portal_options: portal_options_with_defaults(b.get("PortalOptions").cloned()),
1449            description: b
1450                .get("Description")
1451                .and_then(Value::as_str)
1452                .map(str::to_string),
1453            created_date: now(),
1454            created_from: Some(req.region.clone()),
1455            assignments: Vec::new(),
1456            assignment_required: None,
1457            access_scopes: Default::default(),
1458            auth_methods: Default::default(),
1459            grants: Default::default(),
1460            session_status: None,
1461        };
1462        acct.applications.insert(arn.clone(), app);
1463        store_tags(acct, &arn, &b);
1464        ok(json!({
1465            "ApplicationArn": arn,
1466            "InstanceArn": instance_arn,
1467            "IdentityStoreArn": identity_store_arn,
1468        }))
1469    }
1470
1471    fn describe_application(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1472        let b = parse(req)?;
1473        validate_common(&b)?;
1474        let arn = req_str(&b, "ApplicationArn")?.to_string();
1475        let guard = self.state.read();
1476        let app = self
1477            .application(&guard, &req.account_id, &arn)
1478            .ok_or_else(|| not_found("Application not found."))?;
1479        ok(build_application(app))
1480    }
1481
1482    fn update_application(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1483        let b = parse(req)?;
1484        validate_common(&b)?;
1485        check_len(&b, "Name", 1, 100)?;
1486        check_len(&b, "Description", 1, 128)?;
1487        let arn = req_str(&b, "ApplicationArn")?.to_string();
1488        let mut guard = self.state.write();
1489        let app = guard
1490            .get_mut(&req.account_id)
1491            .and_then(|a| a.applications.get_mut(&arn))
1492            .ok_or_else(|| not_found("Application not found."))?;
1493        if let Some(n) = b.get("Name").and_then(Value::as_str) {
1494            app.name = Some(n.to_string());
1495        }
1496        if let Some(d) = b.get("Description").and_then(Value::as_str) {
1497            app.description = Some(d.to_string());
1498        }
1499        if let Some(s) = b.get("Status").and_then(Value::as_str) {
1500            app.status = Some(s.to_string());
1501        }
1502        if let Some(po) = b.get("PortalOptions") {
1503            app.portal_options = portal_options_with_defaults(Some(po.clone()));
1504        }
1505        ok(json!({}))
1506    }
1507
1508    fn delete_application(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1509        let b = parse(req)?;
1510        validate_common(&b)?;
1511        let arn = req_str(&b, "ApplicationArn")?.to_string();
1512        if let Some(a) = self.state.write().get_mut(&req.account_id) {
1513            a.applications.remove(&arn);
1514        }
1515        ok(json!({}))
1516    }
1517
1518    fn list_applications(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1519        let b = parse(req)?;
1520        validate_common(&b)?;
1521        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
1522        let guard = self.state.read();
1523        let rows: Vec<Value> = guard
1524            .get(&req.account_id)
1525            .map(|a| {
1526                a.applications
1527                    .values()
1528                    .filter(|app| app.instance_arn == instance_arn)
1529                    .map(build_application)
1530                    .collect()
1531            })
1532            .unwrap_or_default();
1533        list_response("Applications", rows, &b)
1534    }
1535
1536    // ---------- application assignments ----------
1537
1538    fn with_application<F>(
1539        &self,
1540        req: &AwsRequest,
1541        b: &Value,
1542        f: F,
1543    ) -> Result<AwsResponse, AwsServiceError>
1544    where
1545        F: FnOnce(&mut StoredApplication) -> Result<AwsResponse, AwsServiceError>,
1546    {
1547        let arn = req_str(b, "ApplicationArn")?.to_string();
1548        let mut guard = self.state.write();
1549        let app = guard
1550            .get_mut(&req.account_id)
1551            .and_then(|a| a.applications.get_mut(&arn))
1552            .ok_or_else(|| not_found("Application not found."))?;
1553        f(app)
1554    }
1555
1556    fn create_application_assignment(
1557        &self,
1558        req: &AwsRequest,
1559    ) -> Result<AwsResponse, AwsServiceError> {
1560        let b = parse(req)?;
1561        validate_common(&b)?;
1562        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1563        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1564        self.with_application(req, &b, |app| {
1565            if !app
1566                .assignments
1567                .iter()
1568                .any(|(pid, pt)| pid == &principal_id && pt == &principal_type)
1569            {
1570                app.assignments.push((principal_id, principal_type));
1571            }
1572            ok(json!({}))
1573        })
1574    }
1575
1576    fn delete_application_assignment(
1577        &self,
1578        req: &AwsRequest,
1579    ) -> Result<AwsResponse, AwsServiceError> {
1580        let b = parse(req)?;
1581        validate_common(&b)?;
1582        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1583        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1584        self.with_application(req, &b, |app| {
1585            app.assignments
1586                .retain(|(pid, pt)| !(pid == &principal_id && pt == &principal_type));
1587            ok(json!({}))
1588        })
1589    }
1590
1591    fn describe_application_assignment(
1592        &self,
1593        req: &AwsRequest,
1594    ) -> Result<AwsResponse, AwsServiceError> {
1595        let b = parse(req)?;
1596        validate_common(&b)?;
1597        let arn = req_str(&b, "ApplicationArn")?.to_string();
1598        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1599        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1600        let guard = self.state.read();
1601        let app = self
1602            .application(&guard, &req.account_id, &arn)
1603            .ok_or_else(|| not_found("Application not found."))?;
1604        let found = app
1605            .assignments
1606            .iter()
1607            .any(|(pid, pt)| pid == &principal_id && pt == &principal_type);
1608        if !found {
1609            return Err(not_found("Application assignment not found."));
1610        }
1611        ok(json!({
1612            "ApplicationArn": arn,
1613            "PrincipalId": principal_id,
1614            "PrincipalType": principal_type,
1615        }))
1616    }
1617
1618    fn list_application_assignments(
1619        &self,
1620        req: &AwsRequest,
1621    ) -> Result<AwsResponse, AwsServiceError> {
1622        let b = parse(req)?;
1623        validate_common(&b)?;
1624        let arn = req_str(&b, "ApplicationArn")?.to_string();
1625        let guard = self.state.read();
1626        let rows: Vec<Value> = self
1627            .application(&guard, &req.account_id, &arn)
1628            .map(|app| {
1629                app.assignments
1630                    .iter()
1631                    .map(|(pid, pt)| {
1632                        json!({ "ApplicationArn": arn, "PrincipalId": pid, "PrincipalType": pt })
1633                    })
1634                    .collect()
1635            })
1636            .unwrap_or_default();
1637        list_response("ApplicationAssignments", rows, &b)
1638    }
1639
1640    fn list_application_assignments_for_principal(
1641        &self,
1642        req: &AwsRequest,
1643    ) -> Result<AwsResponse, AwsServiceError> {
1644        let b = parse(req)?;
1645        validate_common(&b)?;
1646        req_str(&b, "InstanceArn")?;
1647        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1648        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1649        let guard = self.state.read();
1650        let rows: Vec<Value> = guard
1651            .get(&req.account_id)
1652            .map(|a| {
1653                a.applications
1654                    .values()
1655                    .flat_map(|app| {
1656                        app.assignments
1657                            .iter()
1658                            .filter(|(pid, pt)| pid == &principal_id && pt == &principal_type)
1659                            .map(|(pid, pt)| {
1660                                json!({
1661                                    "ApplicationArn": app.arn,
1662                                    "PrincipalId": pid,
1663                                    "PrincipalType": pt,
1664                                })
1665                            })
1666                            .collect::<Vec<_>>()
1667                    })
1668                    .collect()
1669            })
1670            .unwrap_or_default();
1671        list_response("ApplicationAssignments", rows, &b)
1672    }
1673
1674    fn get_application_assignment_configuration(
1675        &self,
1676        req: &AwsRequest,
1677    ) -> Result<AwsResponse, AwsServiceError> {
1678        let b = parse(req)?;
1679        validate_common(&b)?;
1680        let arn = req_str(&b, "ApplicationArn")?.to_string();
1681        let guard = self.state.read();
1682        let app = self
1683            .application(&guard, &req.account_id, &arn)
1684            .ok_or_else(|| not_found("Application not found."))?;
1685        // AWS defaults AssignmentRequired to true when unset.
1686        ok(json!({ "AssignmentRequired": app.assignment_required.unwrap_or(true) }))
1687    }
1688
1689    fn put_application_assignment_configuration(
1690        &self,
1691        req: &AwsRequest,
1692    ) -> Result<AwsResponse, AwsServiceError> {
1693        let b = parse(req)?;
1694        validate_common(&b)?;
1695        let required = b
1696            .get("AssignmentRequired")
1697            .and_then(Value::as_bool)
1698            .ok_or_else(|| validation("AssignmentRequired must be specified."))?;
1699        self.with_application(req, &b, |app| {
1700            app.assignment_required = Some(required);
1701            ok(json!({}))
1702        })
1703    }
1704
1705    // ---------- application access scopes ----------
1706
1707    fn put_application_access_scope(
1708        &self,
1709        req: &AwsRequest,
1710    ) -> Result<AwsResponse, AwsServiceError> {
1711        let b = parse(req)?;
1712        validate_common(&b)?;
1713        let scope = req_str(&b, "Scope")?.to_string();
1714        let targets = b.get("AuthorizedTargets").cloned();
1715        self.with_application(req, &b, |app| {
1716            app.access_scopes
1717                .insert(scope, targets.unwrap_or(Value::Null));
1718            ok(json!({}))
1719        })
1720    }
1721
1722    fn get_application_access_scope(
1723        &self,
1724        req: &AwsRequest,
1725    ) -> Result<AwsResponse, AwsServiceError> {
1726        let b = parse(req)?;
1727        validate_common(&b)?;
1728        let arn = req_str(&b, "ApplicationArn")?.to_string();
1729        let scope = req_str(&b, "Scope")?.to_string();
1730        let guard = self.state.read();
1731        let app = self
1732            .application(&guard, &req.account_id, &arn)
1733            .ok_or_else(|| not_found("Application not found."))?;
1734        let targets = app
1735            .access_scopes
1736            .get(&scope)
1737            .ok_or_else(|| not_found("Access scope not found."))?;
1738        let mut m = serde_json::Map::new();
1739        m.insert("Scope".into(), json!(scope));
1740        if !targets.is_null() {
1741            m.insert("AuthorizedTargets".into(), targets.clone());
1742        }
1743        ok(Value::Object(m))
1744    }
1745
1746    fn delete_application_access_scope(
1747        &self,
1748        req: &AwsRequest,
1749    ) -> Result<AwsResponse, AwsServiceError> {
1750        let b = parse(req)?;
1751        validate_common(&b)?;
1752        let scope = req_str(&b, "Scope")?.to_string();
1753        self.with_application(req, &b, |app| {
1754            app.access_scopes.remove(&scope);
1755            ok(json!({}))
1756        })
1757    }
1758
1759    fn list_application_access_scopes(
1760        &self,
1761        req: &AwsRequest,
1762    ) -> Result<AwsResponse, AwsServiceError> {
1763        let b = parse(req)?;
1764        validate_common(&b)?;
1765        let arn = req_str(&b, "ApplicationArn")?.to_string();
1766        let guard = self.state.read();
1767        let rows: Vec<Value> = self
1768            .application(&guard, &req.account_id, &arn)
1769            .map(|app| {
1770                app.access_scopes
1771                    .iter()
1772                    .map(|(scope, targets)| {
1773                        let mut m = serde_json::Map::new();
1774                        m.insert("Scope".into(), json!(scope));
1775                        if !targets.is_null() {
1776                            m.insert("AuthorizedTargets".into(), targets.clone());
1777                        }
1778                        Value::Object(m)
1779                    })
1780                    .collect()
1781            })
1782            .unwrap_or_default();
1783        list_response("Scopes", rows, &b)
1784    }
1785
1786    // ---------- application authentication methods ----------
1787
1788    fn put_application_authentication_method(
1789        &self,
1790        req: &AwsRequest,
1791    ) -> Result<AwsResponse, AwsServiceError> {
1792        let b = parse(req)?;
1793        validate_common(&b)?;
1794        let mtype = req_str(&b, "AuthenticationMethodType")?.to_string();
1795        let method = b
1796            .get("AuthenticationMethod")
1797            .cloned()
1798            .ok_or_else(|| validation("AuthenticationMethod must be specified."))?;
1799        self.with_application(req, &b, |app| {
1800            app.auth_methods.insert(mtype, method);
1801            ok(json!({}))
1802        })
1803    }
1804
1805    fn get_application_authentication_method(
1806        &self,
1807        req: &AwsRequest,
1808    ) -> Result<AwsResponse, AwsServiceError> {
1809        let b = parse(req)?;
1810        validate_common(&b)?;
1811        let arn = req_str(&b, "ApplicationArn")?.to_string();
1812        let mtype = req_str(&b, "AuthenticationMethodType")?.to_string();
1813        let guard = self.state.read();
1814        let app = self
1815            .application(&guard, &req.account_id, &arn)
1816            .ok_or_else(|| not_found("Application not found."))?;
1817        let method = app
1818            .auth_methods
1819            .get(&mtype)
1820            .ok_or_else(|| not_found("Authentication method not found."))?;
1821        ok(json!({ "AuthenticationMethod": method }))
1822    }
1823
1824    fn delete_application_authentication_method(
1825        &self,
1826        req: &AwsRequest,
1827    ) -> Result<AwsResponse, AwsServiceError> {
1828        let b = parse(req)?;
1829        validate_common(&b)?;
1830        let mtype = req_str(&b, "AuthenticationMethodType")?.to_string();
1831        self.with_application(req, &b, |app| {
1832            app.auth_methods.remove(&mtype);
1833            ok(json!({}))
1834        })
1835    }
1836
1837    fn list_application_authentication_methods(
1838        &self,
1839        req: &AwsRequest,
1840    ) -> Result<AwsResponse, AwsServiceError> {
1841        let b = parse(req)?;
1842        validate_common(&b)?;
1843        let arn = req_str(&b, "ApplicationArn")?.to_string();
1844        let guard = self.state.read();
1845        let rows: Vec<Value> = self
1846            .application(&guard, &req.account_id, &arn)
1847            .map(|app| {
1848                app.auth_methods
1849                    .iter()
1850                    .map(|(t, m)| json!({ "AuthenticationMethodType": t, "AuthenticationMethod": m }))
1851                    .collect()
1852            })
1853            .unwrap_or_default();
1854        list_response("AuthenticationMethods", rows, &b)
1855    }
1856
1857    // ---------- application grants ----------
1858
1859    fn put_application_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1860        let b = parse(req)?;
1861        validate_common(&b)?;
1862        let gtype = req_str(&b, "GrantType")?.to_string();
1863        let grant = b
1864            .get("Grant")
1865            .cloned()
1866            .ok_or_else(|| validation("Grant must be specified."))?;
1867        self.with_application(req, &b, |app| {
1868            app.grants.insert(gtype, grant);
1869            ok(json!({}))
1870        })
1871    }
1872
1873    fn get_application_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1874        let b = parse(req)?;
1875        validate_common(&b)?;
1876        let arn = req_str(&b, "ApplicationArn")?.to_string();
1877        let gtype = req_str(&b, "GrantType")?.to_string();
1878        let guard = self.state.read();
1879        let app = self
1880            .application(&guard, &req.account_id, &arn)
1881            .ok_or_else(|| not_found("Application not found."))?;
1882        let grant = app
1883            .grants
1884            .get(&gtype)
1885            .ok_or_else(|| not_found("Grant not found."))?;
1886        ok(json!({ "Grant": grant }))
1887    }
1888
1889    fn delete_application_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1890        let b = parse(req)?;
1891        validate_common(&b)?;
1892        let gtype = req_str(&b, "GrantType")?.to_string();
1893        self.with_application(req, &b, |app| {
1894            app.grants.remove(&gtype);
1895            ok(json!({}))
1896        })
1897    }
1898
1899    fn list_application_grants(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1900        let b = parse(req)?;
1901        validate_common(&b)?;
1902        let arn = req_str(&b, "ApplicationArn")?.to_string();
1903        let guard = self.state.read();
1904        let rows: Vec<Value> = self
1905            .application(&guard, &req.account_id, &arn)
1906            .map(|app| {
1907                app.grants
1908                    .iter()
1909                    .map(|(t, g)| json!({ "GrantType": t, "Grant": g }))
1910                    .collect()
1911            })
1912            .unwrap_or_default();
1913        list_response("Grants", rows, &b)
1914    }
1915
1916    // ---------- application session configuration ----------
1917
1918    fn get_application_session_configuration(
1919        &self,
1920        req: &AwsRequest,
1921    ) -> Result<AwsResponse, AwsServiceError> {
1922        let b = parse(req)?;
1923        validate_common(&b)?;
1924        let arn = req_str(&b, "ApplicationArn")?.to_string();
1925        let guard = self.state.read();
1926        let app = self
1927            .application(&guard, &req.account_id, &arn)
1928            .ok_or_else(|| not_found("Application not found."))?;
1929        let mut m = serde_json::Map::new();
1930        if let Some(status) = &app.session_status {
1931            m.insert(
1932                "UserBackgroundSessionApplicationStatus".into(),
1933                json!(status),
1934            );
1935        }
1936        ok(Value::Object(m))
1937    }
1938
1939    fn put_application_session_configuration(
1940        &self,
1941        req: &AwsRequest,
1942    ) -> Result<AwsResponse, AwsServiceError> {
1943        let b = parse(req)?;
1944        validate_common(&b)?;
1945        let status = b
1946            .get("UserBackgroundSessionApplicationStatus")
1947            .and_then(Value::as_str)
1948            .map(str::to_string);
1949        self.with_application(req, &b, |app| {
1950            app.session_status = status;
1951            ok(json!({}))
1952        })
1953    }
1954
1955    // ---------- application providers ----------
1956
1957    fn list_application_providers(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1958        let b = parse(req)?;
1959        validate_common(&b)?;
1960        let rows: Vec<Value> = APPLICATION_PROVIDERS
1961            .iter()
1962            .map(|(arn, proto, display)| {
1963                json!({
1964                    "ApplicationProviderArn": arn,
1965                    "FederationProtocol": proto,
1966                    "DisplayData": { "DisplayName": display },
1967                })
1968            })
1969            .collect();
1970        list_response("ApplicationProviders", rows, &b)
1971    }
1972
1973    fn describe_application_provider(
1974        &self,
1975        req: &AwsRequest,
1976    ) -> Result<AwsResponse, AwsServiceError> {
1977        let b = parse(req)?;
1978        validate_common(&b)?;
1979        let arn = req_str(&b, "ApplicationProviderArn")?.to_string();
1980        let found = APPLICATION_PROVIDERS
1981            .iter()
1982            .find(|(a, _, _)| *a == arn)
1983            .ok_or_else(|| not_found("Application provider not found."))?;
1984        ok(json!({
1985            "ApplicationProviderArn": found.0,
1986            "FederationProtocol": found.1,
1987            "DisplayData": { "DisplayName": found.2 },
1988        }))
1989    }
1990
1991    // ---------- trusted token issuers ----------
1992
1993    fn create_trusted_token_issuer(
1994        &self,
1995        req: &AwsRequest,
1996    ) -> Result<AwsResponse, AwsServiceError> {
1997        let b = parse(req)?;
1998        validate_common(&b)?;
1999        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2000        let name = req_str(&b, "Name")?.to_string();
2001        check_len(&b, "Name", 1, 255)?;
2002        req_str(&b, "TrustedTokenIssuerType")?;
2003        // `CreateTrustedTokenIssuer` declares no ResourceNotFoundException, so
2004        // synthesize the parent instance's fragment when it's not on record.
2005        let guard = self.state.read();
2006        let ssoins = guard
2007            .get(&req.account_id)
2008            .and_then(|a| a.instances.get(&instance_arn))
2009            .map(|i| i.ssoins.clone())
2010            .unwrap_or_else(|| format!("ssoins-{}", hex16()));
2011        drop(guard);
2012        let arn = format!(
2013            "arn:aws:sso::{}:trustedTokenIssuer/{ssoins}/tti-{}",
2014            req.account_id,
2015            Uuid::new_v4()
2016        );
2017        let tti = StoredTrustedTokenIssuer {
2018            arn: arn.clone(),
2019            instance_arn,
2020            name: Some(name),
2021            issuer_type: b
2022                .get("TrustedTokenIssuerType")
2023                .and_then(Value::as_str)
2024                .unwrap_or("OIDC_JWT")
2025                .to_string(),
2026            config: b.get("TrustedTokenIssuerConfiguration").cloned(),
2027        };
2028        {
2029            let mut guard = self.state.write();
2030            let acct = guard.get_or_create(&req.account_id);
2031            acct.trusted_token_issuers.insert(arn.clone(), tti);
2032            store_tags(acct, &arn, &b);
2033        }
2034        ok(json!({ "TrustedTokenIssuerArn": arn }))
2035    }
2036
2037    fn describe_trusted_token_issuer(
2038        &self,
2039        req: &AwsRequest,
2040    ) -> Result<AwsResponse, AwsServiceError> {
2041        let b = parse(req)?;
2042        validate_common(&b)?;
2043        let arn = req_str(&b, "TrustedTokenIssuerArn")?.to_string();
2044        let guard = self.state.read();
2045        let tti = guard
2046            .get(&req.account_id)
2047            .and_then(|a| a.trusted_token_issuers.get(&arn))
2048            .ok_or_else(|| not_found("Trusted token issuer not found."))?;
2049        ok(build_tti_describe(tti))
2050    }
2051
2052    fn update_trusted_token_issuer(
2053        &self,
2054        req: &AwsRequest,
2055    ) -> Result<AwsResponse, AwsServiceError> {
2056        let b = parse(req)?;
2057        validate_common(&b)?;
2058        check_len(&b, "Name", 1, 255)?;
2059        let arn = req_str(&b, "TrustedTokenIssuerArn")?.to_string();
2060        let mut guard = self.state.write();
2061        let tti = guard
2062            .get_mut(&req.account_id)
2063            .and_then(|a| a.trusted_token_issuers.get_mut(&arn))
2064            .ok_or_else(|| not_found("Trusted token issuer not found."))?;
2065        if let Some(n) = b.get("Name").and_then(Value::as_str) {
2066            tti.name = Some(n.to_string());
2067        }
2068        if let Some(c) = b.get("TrustedTokenIssuerConfiguration") {
2069            // The update configuration omits the immutable `IssuerUrl`; merge so
2070            // the stored describe config keeps it instead of dropping it (which
2071            // would make Terraform see a perpetual replacement diff).
2072            match &mut tti.config {
2073                Some(existing) => deep_merge(existing, c),
2074                None => tti.config = Some(c.clone()),
2075            }
2076        }
2077        ok(json!({}))
2078    }
2079
2080    fn delete_trusted_token_issuer(
2081        &self,
2082        req: &AwsRequest,
2083    ) -> Result<AwsResponse, AwsServiceError> {
2084        let b = parse(req)?;
2085        validate_common(&b)?;
2086        let arn = req_str(&b, "TrustedTokenIssuerArn")?.to_string();
2087        if let Some(a) = self.state.write().get_mut(&req.account_id) {
2088            a.trusted_token_issuers.remove(&arn);
2089        }
2090        ok(json!({}))
2091    }
2092
2093    fn list_trusted_token_issuers(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2094        let b = parse(req)?;
2095        validate_common(&b)?;
2096        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2097        let guard = self.state.read();
2098        let rows: Vec<Value> = guard
2099            .get(&req.account_id)
2100            .map(|a| {
2101                a.trusted_token_issuers
2102                    .values()
2103                    .filter(|t| t.instance_arn == instance_arn)
2104                    .map(build_tti_metadata)
2105                    .collect()
2106            })
2107            .unwrap_or_default();
2108        list_response("TrustedTokenIssuers", rows, &b)
2109    }
2110
2111    // ---------- regions ----------
2112
2113    fn add_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2114        let b = parse(req)?;
2115        validate_common(&b)?;
2116        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2117        let region_name = req_str(&b, "RegionName")?.to_string();
2118        let mut guard = self.state.write();
2119        let acct = guard.get_or_create(&req.account_id);
2120        // `AddRegion` declares no ResourceNotFoundException, so materialize the
2121        // instance record if it isn't already present.
2122        let ssoins = instance_arn
2123            .rsplit('/')
2124            .next()
2125            .unwrap_or("ssoins-unknown")
2126            .to_string();
2127        let inst = acct
2128            .instances
2129            .entry(instance_arn.clone())
2130            .or_insert_with(|| StoredInstance {
2131                arn: instance_arn.clone(),
2132                ssoins,
2133                identity_store_id: format!("d-{}", hex10()),
2134                owner_account_id: req.account_id.clone(),
2135                name: None,
2136                status: "ACTIVE".into(),
2137                created_date: now(),
2138                regions: Vec::new(),
2139                primary_region: Some(req.region.clone()),
2140                attr_config: None,
2141                attr_config_status: None,
2142            });
2143        if !inst.regions.iter().any(|r| r.region_name == region_name) {
2144            let is_primary = inst.regions.is_empty();
2145            if is_primary {
2146                inst.primary_region = Some(region_name.clone());
2147            }
2148            inst.regions.push(StoredRegion {
2149                region_name,
2150                status: "ACTIVE".into(),
2151                added_date: now(),
2152                is_primary,
2153            });
2154        }
2155        ok(json!({ "Status": "ACTIVE" }))
2156    }
2157
2158    fn remove_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2159        let b = parse(req)?;
2160        validate_common(&b)?;
2161        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2162        let region_name = req_str(&b, "RegionName")?.to_string();
2163        if let Some(inst) = self
2164            .state
2165            .write()
2166            .get_mut(&req.account_id)
2167            .and_then(|a| a.instances.get_mut(&instance_arn))
2168        {
2169            inst.regions.retain(|r| r.region_name != region_name);
2170        }
2171        ok(json!({ "Status": "REMOVING" }))
2172    }
2173
2174    fn describe_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2175        let b = parse(req)?;
2176        validate_common(&b)?;
2177        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2178        let region_name = req_str(&b, "RegionName")?.to_string();
2179        let guard = self.state.read();
2180        let region = guard
2181            .get(&req.account_id)
2182            .and_then(|a| a.instances.get(&instance_arn))
2183            .and_then(|i| i.regions.iter().find(|r| r.region_name == region_name))
2184            .ok_or_else(|| not_found("Region not found."))?;
2185        ok(build_region(region))
2186    }
2187
2188    fn list_regions(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2189        let b = parse(req)?;
2190        validate_common(&b)?;
2191        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2192        let guard = self.state.read();
2193        let rows: Vec<Value> = guard
2194            .get(&req.account_id)
2195            .and_then(|a| a.instances.get(&instance_arn))
2196            .map(|i| i.regions.iter().map(build_region).collect())
2197            .unwrap_or_default();
2198        list_response("Regions", rows, &b)
2199    }
2200
2201    // ---------- access control attribute configuration ----------
2202
2203    fn create_attr_config(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2204        let b = parse(req)?;
2205        validate_common(&b)?;
2206        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2207        let config = b
2208            .get("InstanceAccessControlAttributeConfiguration")
2209            .cloned()
2210            .ok_or_else(|| {
2211                validation("InstanceAccessControlAttributeConfiguration must be specified.")
2212            })?;
2213        let mut guard = self.state.write();
2214        let inst = guard
2215            .get_mut(&req.account_id)
2216            .and_then(|a| a.instances.get_mut(&instance_arn))
2217            .ok_or_else(|| not_found("Instance not found."))?;
2218        inst.attr_config = Some(config);
2219        inst.attr_config_status = Some("ENABLED".into());
2220        ok(json!({}))
2221    }
2222
2223    fn describe_attr_config(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2224        let b = parse(req)?;
2225        validate_common(&b)?;
2226        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2227        let guard = self.state.read();
2228        let inst = guard
2229            .get(&req.account_id)
2230            .and_then(|a| a.instances.get(&instance_arn))
2231            .ok_or_else(|| not_found("Instance not found."))?;
2232        let config = inst
2233            .attr_config
2234            .clone()
2235            .ok_or_else(|| not_found("Access control attribute configuration not found."))?;
2236        ok(json!({
2237            "Status": inst.attr_config_status.clone().unwrap_or_else(|| "ENABLED".into()),
2238            "InstanceAccessControlAttributeConfiguration": config,
2239        }))
2240    }
2241
2242    fn update_attr_config(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2243        let b = parse(req)?;
2244        validate_common(&b)?;
2245        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2246        let config = b
2247            .get("InstanceAccessControlAttributeConfiguration")
2248            .cloned()
2249            .ok_or_else(|| {
2250                validation("InstanceAccessControlAttributeConfiguration must be specified.")
2251            })?;
2252        let mut guard = self.state.write();
2253        let inst = guard
2254            .get_mut(&req.account_id)
2255            .and_then(|a| a.instances.get_mut(&instance_arn))
2256            .ok_or_else(|| not_found("Instance not found."))?;
2257        inst.attr_config = Some(config);
2258        inst.attr_config_status = Some("ENABLED".into());
2259        ok(json!({}))
2260    }
2261
2262    fn delete_attr_config(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2263        let b = parse(req)?;
2264        validate_common(&b)?;
2265        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2266        if let Some(inst) = self
2267            .state
2268            .write()
2269            .get_mut(&req.account_id)
2270            .and_then(|a| a.instances.get_mut(&instance_arn))
2271        {
2272            inst.attr_config = None;
2273            inst.attr_config_status = None;
2274        }
2275        ok(json!({}))
2276    }
2277
2278    // ---------- tagging ----------
2279
2280    fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2281        let b = parse(req)?;
2282        validate_common(&b)?;
2283        let resource_arn = req_str(&b, "ResourceArn")?.to_string();
2284        let tags = b
2285            .get("Tags")
2286            .and_then(Value::as_array)
2287            .ok_or_else(|| validation("Tags must be specified."))?;
2288        let mut guard = self.state.write();
2289        let entry = guard
2290            .get_or_create(&req.account_id)
2291            .tags
2292            .entry(resource_arn)
2293            .or_default();
2294        for tag in tags {
2295            if let (Some(k), Some(v)) = (
2296                tag.get("Key").and_then(Value::as_str),
2297                tag.get("Value").and_then(Value::as_str),
2298            ) {
2299                entry.insert(k.to_string(), v.to_string());
2300            }
2301        }
2302        ok(json!({}))
2303    }
2304
2305    fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2306        let b = parse(req)?;
2307        validate_common(&b)?;
2308        let resource_arn = req_str(&b, "ResourceArn")?.to_string();
2309        let keys = b
2310            .get("TagKeys")
2311            .and_then(Value::as_array)
2312            .ok_or_else(|| validation("TagKeys must be specified."))?;
2313        if let Some(entry) = self
2314            .state
2315            .write()
2316            .get_mut(&req.account_id)
2317            .and_then(|a| a.tags.get_mut(&resource_arn))
2318        {
2319            for k in keys {
2320                if let Some(k) = k.as_str() {
2321                    entry.remove(k);
2322                }
2323            }
2324        }
2325        ok(json!({}))
2326    }
2327
2328    fn list_tags_for_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2329        let b = parse(req)?;
2330        validate_common(&b)?;
2331        let resource_arn = req_str(&b, "ResourceArn")?.to_string();
2332        let guard = self.state.read();
2333        let rows: Vec<Value> = guard
2334            .get(&req.account_id)
2335            .and_then(|a| a.tags.get(&resource_arn))
2336            .map(|m| {
2337                m.iter()
2338                    .map(|(k, v)| json!({ "Key": k, "Value": v }))
2339                    .collect()
2340            })
2341            .unwrap_or_default();
2342        list_response("Tags", rows, &b)
2343    }
2344}
2345
2346#[cfg(test)]
2347mod tests;