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        let principal_type = req_str(&b, "PrincipalType")?.to_string();
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                    // `PrincipalType` is a required input and AWS filters on it
1271                    // together with `PrincipalId`; ignoring it would surface a
1272                    // USER assignment when the caller queried a GROUP (and vice
1273                    // versa) whose id happened to collide.
1274                    .filter(|x| {
1275                        x.principal_id == principal_id && x.principal_type == principal_type
1276                    })
1277                    .map(|x| {
1278                        json!({
1279                            "AccountId": x.account_id,
1280                            "PermissionSetArn": x.permission_set_arn,
1281                            "PrincipalId": x.principal_id,
1282                            "PrincipalType": x.principal_type,
1283                        })
1284                    })
1285                    .collect()
1286            })
1287            .unwrap_or_default();
1288        list_response("AccountAssignments", rows, &b)
1289    }
1290
1291    fn list_accounts_for_provisioned(
1292        &self,
1293        req: &AwsRequest,
1294    ) -> Result<AwsResponse, AwsServiceError> {
1295        let b = parse(req)?;
1296        validate_common(&b)?;
1297        req_str(&b, "InstanceArn")?;
1298        let ps_arn = req_str(&b, "PermissionSetArn")?.to_string();
1299        let guard = self.state.read();
1300        let mut accounts: Vec<String> = guard
1301            .get(&req.account_id)
1302            .map(|a| {
1303                a.assignments
1304                    .iter()
1305                    .filter(|x| x.permission_set_arn == ps_arn)
1306                    .map(|x| x.account_id.clone())
1307                    .collect()
1308            })
1309            .unwrap_or_default();
1310        accounts.sort();
1311        accounts.dedup();
1312        let rows: Vec<Value> = accounts.into_iter().map(|a| json!(a)).collect();
1313        list_response("AccountIds", rows, &b)
1314    }
1315
1316    fn list_ps_provisioned_to_account(
1317        &self,
1318        req: &AwsRequest,
1319    ) -> Result<AwsResponse, AwsServiceError> {
1320        let b = parse(req)?;
1321        validate_common(&b)?;
1322        req_str(&b, "InstanceArn")?;
1323        let account_id = req_str(&b, "AccountId")?.to_string();
1324        let guard = self.state.read();
1325        let mut ps: Vec<String> = guard
1326            .get(&req.account_id)
1327            .map(|a| {
1328                a.assignments
1329                    .iter()
1330                    .filter(|x| x.account_id == account_id)
1331                    .map(|x| x.permission_set_arn.clone())
1332                    .collect()
1333            })
1334            .unwrap_or_default();
1335        ps.sort();
1336        ps.dedup();
1337        let rows: Vec<Value> = ps.into_iter().map(|a| json!(a)).collect();
1338        list_response("PermissionSets", rows, &b)
1339    }
1340
1341    // ---------- provisioning ----------
1342
1343    fn provision_permission_set(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1344        let b = parse(req)?;
1345        validate_common(&b)?;
1346        req_str(&b, "InstanceArn")?;
1347        let ps_arn = req_str(&b, "PermissionSetArn")?.to_string();
1348        req_str(&b, "TargetType")?;
1349        let request_id = Uuid::new_v4().to_string();
1350        let st = StoredProvisioningStatus {
1351            request_id: request_id.clone(),
1352            status: "IN_PROGRESS".into(),
1353            account_id: b
1354                .get("TargetId")
1355                .and_then(Value::as_str)
1356                .map(str::to_string),
1357            permission_set_arn: ps_arn,
1358            created_date: now(),
1359        };
1360        self.state
1361            .write()
1362            .get_or_create(&req.account_id)
1363            .ps_provisioning
1364            .insert(request_id, st.clone());
1365        ok(json!({ "PermissionSetProvisioningStatus": build_prov_status(&st) }))
1366    }
1367
1368    fn describe_ps_provisioning_status(
1369        &self,
1370        req: &AwsRequest,
1371    ) -> Result<AwsResponse, AwsServiceError> {
1372        let b = parse(req)?;
1373        validate_common(&b)?;
1374        req_str(&b, "InstanceArn")?;
1375        let id = req_str(&b, "ProvisionPermissionSetRequestId")?.to_string();
1376        let mut guard = self.state.write();
1377        let st = guard
1378            .get_mut(&req.account_id)
1379            .and_then(|a| a.ps_provisioning.get_mut(&id))
1380            .ok_or_else(|| not_found("Provisioning status not found."))?;
1381        st.status = "SUCCEEDED".into();
1382        ok(json!({ "PermissionSetProvisioningStatus": build_prov_status(st) }))
1383    }
1384
1385    fn list_ps_provisioning_status(
1386        &self,
1387        req: &AwsRequest,
1388    ) -> Result<AwsResponse, AwsServiceError> {
1389        let b = parse(req)?;
1390        validate_common(&b)?;
1391        req_str(&b, "InstanceArn")?;
1392        let guard = self.state.read();
1393        let rows: Vec<Value> = guard
1394            .get(&req.account_id)
1395            .map(|a| {
1396                a.ps_provisioning
1397                    .values()
1398                    .map(|st| json!({ "Status": st.status, "RequestId": st.request_id, "CreatedDate": st.created_date }))
1399                    .collect()
1400            })
1401            .unwrap_or_default();
1402        list_response("PermissionSetsProvisioningStatus", rows, &b)
1403    }
1404
1405    // ---------- applications ----------
1406
1407    fn application<'a>(
1408        &self,
1409        guard: &'a fakecloud_core::multi_account::MultiAccountState<crate::state::SsoAdminData>,
1410        account: &str,
1411        arn: &str,
1412    ) -> Option<&'a StoredApplication> {
1413        guard.get(account).and_then(|a| a.applications.get(arn))
1414    }
1415
1416    fn create_application(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1417        let b = parse(req)?;
1418        validate_common(&b)?;
1419        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
1420        let provider_arn = req_str(&b, "ApplicationProviderArn")?.to_string();
1421        let name = req_str(&b, "Name")?.to_string();
1422        check_len(&b, "Name", 1, 100)?;
1423        check_len(&b, "Description", 1, 128)?;
1424        let mut guard = self.state.write();
1425        let acct = guard.get_or_create(&req.account_id);
1426        let inst = acct
1427            .instances
1428            .get(&instance_arn)
1429            .ok_or_else(|| not_found("Instance not found."))?;
1430        let ssoins = inst.ssoins.clone();
1431        let identity_store_id = inst.identity_store_id.clone();
1432        let arn = format!(
1433            "arn:aws:sso::{}:application/{ssoins}/apl-{}",
1434            req.account_id,
1435            hex16()
1436        );
1437        let identity_store_arn = format!(
1438            "arn:aws:identitystore::{}:identitystore/{identity_store_id}",
1439            req.account_id
1440        );
1441        let app = StoredApplication {
1442            arn: arn.clone(),
1443            provider_arn,
1444            name: Some(name),
1445            account: req.account_id.clone(),
1446            instance_arn: instance_arn.clone(),
1447            identity_store_arn: identity_store_arn.clone(),
1448            status: Some(
1449                b.get("Status")
1450                    .and_then(Value::as_str)
1451                    .unwrap_or("ENABLED")
1452                    .to_string(),
1453            ),
1454            portal_options: portal_options_with_defaults(b.get("PortalOptions").cloned()),
1455            description: b
1456                .get("Description")
1457                .and_then(Value::as_str)
1458                .map(str::to_string),
1459            created_date: now(),
1460            created_from: Some(req.region.clone()),
1461            assignments: Vec::new(),
1462            assignment_required: None,
1463            access_scopes: Default::default(),
1464            auth_methods: Default::default(),
1465            grants: Default::default(),
1466            session_status: None,
1467        };
1468        acct.applications.insert(arn.clone(), app);
1469        store_tags(acct, &arn, &b);
1470        ok(json!({
1471            "ApplicationArn": arn,
1472            "InstanceArn": instance_arn,
1473            "IdentityStoreArn": identity_store_arn,
1474        }))
1475    }
1476
1477    fn describe_application(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1478        let b = parse(req)?;
1479        validate_common(&b)?;
1480        let arn = req_str(&b, "ApplicationArn")?.to_string();
1481        let guard = self.state.read();
1482        let app = self
1483            .application(&guard, &req.account_id, &arn)
1484            .ok_or_else(|| not_found("Application not found."))?;
1485        ok(build_application(app))
1486    }
1487
1488    fn update_application(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1489        let b = parse(req)?;
1490        validate_common(&b)?;
1491        check_len(&b, "Name", 1, 100)?;
1492        check_len(&b, "Description", 1, 128)?;
1493        let arn = req_str(&b, "ApplicationArn")?.to_string();
1494        let mut guard = self.state.write();
1495        let app = guard
1496            .get_mut(&req.account_id)
1497            .and_then(|a| a.applications.get_mut(&arn))
1498            .ok_or_else(|| not_found("Application not found."))?;
1499        if let Some(n) = b.get("Name").and_then(Value::as_str) {
1500            app.name = Some(n.to_string());
1501        }
1502        if let Some(d) = b.get("Description").and_then(Value::as_str) {
1503            app.description = Some(d.to_string());
1504        }
1505        if let Some(s) = b.get("Status").and_then(Value::as_str) {
1506            app.status = Some(s.to_string());
1507        }
1508        if let Some(po) = b.get("PortalOptions") {
1509            app.portal_options = portal_options_with_defaults(Some(po.clone()));
1510        }
1511        ok(json!({}))
1512    }
1513
1514    fn delete_application(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1515        let b = parse(req)?;
1516        validate_common(&b)?;
1517        let arn = req_str(&b, "ApplicationArn")?.to_string();
1518        if let Some(a) = self.state.write().get_mut(&req.account_id) {
1519            a.applications.remove(&arn);
1520        }
1521        ok(json!({}))
1522    }
1523
1524    fn list_applications(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1525        let b = parse(req)?;
1526        validate_common(&b)?;
1527        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
1528        let guard = self.state.read();
1529        let rows: Vec<Value> = guard
1530            .get(&req.account_id)
1531            .map(|a| {
1532                a.applications
1533                    .values()
1534                    .filter(|app| app.instance_arn == instance_arn)
1535                    .map(build_application)
1536                    .collect()
1537            })
1538            .unwrap_or_default();
1539        list_response("Applications", rows, &b)
1540    }
1541
1542    // ---------- application assignments ----------
1543
1544    fn with_application<F>(
1545        &self,
1546        req: &AwsRequest,
1547        b: &Value,
1548        f: F,
1549    ) -> Result<AwsResponse, AwsServiceError>
1550    where
1551        F: FnOnce(&mut StoredApplication) -> Result<AwsResponse, AwsServiceError>,
1552    {
1553        let arn = req_str(b, "ApplicationArn")?.to_string();
1554        let mut guard = self.state.write();
1555        let app = guard
1556            .get_mut(&req.account_id)
1557            .and_then(|a| a.applications.get_mut(&arn))
1558            .ok_or_else(|| not_found("Application not found."))?;
1559        f(app)
1560    }
1561
1562    fn create_application_assignment(
1563        &self,
1564        req: &AwsRequest,
1565    ) -> Result<AwsResponse, AwsServiceError> {
1566        let b = parse(req)?;
1567        validate_common(&b)?;
1568        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1569        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1570        self.with_application(req, &b, |app| {
1571            if !app
1572                .assignments
1573                .iter()
1574                .any(|(pid, pt)| pid == &principal_id && pt == &principal_type)
1575            {
1576                app.assignments.push((principal_id, principal_type));
1577            }
1578            ok(json!({}))
1579        })
1580    }
1581
1582    fn delete_application_assignment(
1583        &self,
1584        req: &AwsRequest,
1585    ) -> Result<AwsResponse, AwsServiceError> {
1586        let b = parse(req)?;
1587        validate_common(&b)?;
1588        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1589        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1590        self.with_application(req, &b, |app| {
1591            app.assignments
1592                .retain(|(pid, pt)| !(pid == &principal_id && pt == &principal_type));
1593            ok(json!({}))
1594        })
1595    }
1596
1597    fn describe_application_assignment(
1598        &self,
1599        req: &AwsRequest,
1600    ) -> Result<AwsResponse, AwsServiceError> {
1601        let b = parse(req)?;
1602        validate_common(&b)?;
1603        let arn = req_str(&b, "ApplicationArn")?.to_string();
1604        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1605        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1606        let guard = self.state.read();
1607        let app = self
1608            .application(&guard, &req.account_id, &arn)
1609            .ok_or_else(|| not_found("Application not found."))?;
1610        let found = app
1611            .assignments
1612            .iter()
1613            .any(|(pid, pt)| pid == &principal_id && pt == &principal_type);
1614        if !found {
1615            return Err(not_found("Application assignment not found."));
1616        }
1617        ok(json!({
1618            "ApplicationArn": arn,
1619            "PrincipalId": principal_id,
1620            "PrincipalType": principal_type,
1621        }))
1622    }
1623
1624    fn list_application_assignments(
1625        &self,
1626        req: &AwsRequest,
1627    ) -> Result<AwsResponse, AwsServiceError> {
1628        let b = parse(req)?;
1629        validate_common(&b)?;
1630        let arn = req_str(&b, "ApplicationArn")?.to_string();
1631        let guard = self.state.read();
1632        let rows: Vec<Value> = self
1633            .application(&guard, &req.account_id, &arn)
1634            .map(|app| {
1635                app.assignments
1636                    .iter()
1637                    .map(|(pid, pt)| {
1638                        json!({ "ApplicationArn": arn, "PrincipalId": pid, "PrincipalType": pt })
1639                    })
1640                    .collect()
1641            })
1642            .unwrap_or_default();
1643        list_response("ApplicationAssignments", rows, &b)
1644    }
1645
1646    fn list_application_assignments_for_principal(
1647        &self,
1648        req: &AwsRequest,
1649    ) -> Result<AwsResponse, AwsServiceError> {
1650        let b = parse(req)?;
1651        validate_common(&b)?;
1652        req_str(&b, "InstanceArn")?;
1653        let principal_id = req_str(&b, "PrincipalId")?.to_string();
1654        let principal_type = req_str(&b, "PrincipalType")?.to_string();
1655        let guard = self.state.read();
1656        let rows: Vec<Value> = guard
1657            .get(&req.account_id)
1658            .map(|a| {
1659                a.applications
1660                    .values()
1661                    .flat_map(|app| {
1662                        app.assignments
1663                            .iter()
1664                            .filter(|(pid, pt)| pid == &principal_id && pt == &principal_type)
1665                            .map(|(pid, pt)| {
1666                                json!({
1667                                    "ApplicationArn": app.arn,
1668                                    "PrincipalId": pid,
1669                                    "PrincipalType": pt,
1670                                })
1671                            })
1672                            .collect::<Vec<_>>()
1673                    })
1674                    .collect()
1675            })
1676            .unwrap_or_default();
1677        list_response("ApplicationAssignments", rows, &b)
1678    }
1679
1680    fn get_application_assignment_configuration(
1681        &self,
1682        req: &AwsRequest,
1683    ) -> Result<AwsResponse, AwsServiceError> {
1684        let b = parse(req)?;
1685        validate_common(&b)?;
1686        let arn = req_str(&b, "ApplicationArn")?.to_string();
1687        let guard = self.state.read();
1688        let app = self
1689            .application(&guard, &req.account_id, &arn)
1690            .ok_or_else(|| not_found("Application not found."))?;
1691        // AWS defaults AssignmentRequired to true when unset.
1692        ok(json!({ "AssignmentRequired": app.assignment_required.unwrap_or(true) }))
1693    }
1694
1695    fn put_application_assignment_configuration(
1696        &self,
1697        req: &AwsRequest,
1698    ) -> Result<AwsResponse, AwsServiceError> {
1699        let b = parse(req)?;
1700        validate_common(&b)?;
1701        let required = b
1702            .get("AssignmentRequired")
1703            .and_then(Value::as_bool)
1704            .ok_or_else(|| validation("AssignmentRequired must be specified."))?;
1705        self.with_application(req, &b, |app| {
1706            app.assignment_required = Some(required);
1707            ok(json!({}))
1708        })
1709    }
1710
1711    // ---------- application access scopes ----------
1712
1713    fn put_application_access_scope(
1714        &self,
1715        req: &AwsRequest,
1716    ) -> Result<AwsResponse, AwsServiceError> {
1717        let b = parse(req)?;
1718        validate_common(&b)?;
1719        let scope = req_str(&b, "Scope")?.to_string();
1720        let targets = b.get("AuthorizedTargets").cloned();
1721        self.with_application(req, &b, |app| {
1722            app.access_scopes
1723                .insert(scope, targets.unwrap_or(Value::Null));
1724            ok(json!({}))
1725        })
1726    }
1727
1728    fn get_application_access_scope(
1729        &self,
1730        req: &AwsRequest,
1731    ) -> Result<AwsResponse, AwsServiceError> {
1732        let b = parse(req)?;
1733        validate_common(&b)?;
1734        let arn = req_str(&b, "ApplicationArn")?.to_string();
1735        let scope = req_str(&b, "Scope")?.to_string();
1736        let guard = self.state.read();
1737        let app = self
1738            .application(&guard, &req.account_id, &arn)
1739            .ok_or_else(|| not_found("Application not found."))?;
1740        let targets = app
1741            .access_scopes
1742            .get(&scope)
1743            .ok_or_else(|| not_found("Access scope not found."))?;
1744        let mut m = serde_json::Map::new();
1745        m.insert("Scope".into(), json!(scope));
1746        if !targets.is_null() {
1747            m.insert("AuthorizedTargets".into(), targets.clone());
1748        }
1749        ok(Value::Object(m))
1750    }
1751
1752    fn delete_application_access_scope(
1753        &self,
1754        req: &AwsRequest,
1755    ) -> Result<AwsResponse, AwsServiceError> {
1756        let b = parse(req)?;
1757        validate_common(&b)?;
1758        let scope = req_str(&b, "Scope")?.to_string();
1759        self.with_application(req, &b, |app| {
1760            app.access_scopes.remove(&scope);
1761            ok(json!({}))
1762        })
1763    }
1764
1765    fn list_application_access_scopes(
1766        &self,
1767        req: &AwsRequest,
1768    ) -> Result<AwsResponse, AwsServiceError> {
1769        let b = parse(req)?;
1770        validate_common(&b)?;
1771        let arn = req_str(&b, "ApplicationArn")?.to_string();
1772        let guard = self.state.read();
1773        let rows: Vec<Value> = self
1774            .application(&guard, &req.account_id, &arn)
1775            .map(|app| {
1776                app.access_scopes
1777                    .iter()
1778                    .map(|(scope, targets)| {
1779                        let mut m = serde_json::Map::new();
1780                        m.insert("Scope".into(), json!(scope));
1781                        if !targets.is_null() {
1782                            m.insert("AuthorizedTargets".into(), targets.clone());
1783                        }
1784                        Value::Object(m)
1785                    })
1786                    .collect()
1787            })
1788            .unwrap_or_default();
1789        list_response("Scopes", rows, &b)
1790    }
1791
1792    // ---------- application authentication methods ----------
1793
1794    fn put_application_authentication_method(
1795        &self,
1796        req: &AwsRequest,
1797    ) -> Result<AwsResponse, AwsServiceError> {
1798        let b = parse(req)?;
1799        validate_common(&b)?;
1800        let mtype = req_str(&b, "AuthenticationMethodType")?.to_string();
1801        let method = b
1802            .get("AuthenticationMethod")
1803            .cloned()
1804            .ok_or_else(|| validation("AuthenticationMethod must be specified."))?;
1805        self.with_application(req, &b, |app| {
1806            app.auth_methods.insert(mtype, method);
1807            ok(json!({}))
1808        })
1809    }
1810
1811    fn get_application_authentication_method(
1812        &self,
1813        req: &AwsRequest,
1814    ) -> Result<AwsResponse, AwsServiceError> {
1815        let b = parse(req)?;
1816        validate_common(&b)?;
1817        let arn = req_str(&b, "ApplicationArn")?.to_string();
1818        let mtype = req_str(&b, "AuthenticationMethodType")?.to_string();
1819        let guard = self.state.read();
1820        let app = self
1821            .application(&guard, &req.account_id, &arn)
1822            .ok_or_else(|| not_found("Application not found."))?;
1823        let method = app
1824            .auth_methods
1825            .get(&mtype)
1826            .ok_or_else(|| not_found("Authentication method not found."))?;
1827        ok(json!({ "AuthenticationMethod": method }))
1828    }
1829
1830    fn delete_application_authentication_method(
1831        &self,
1832        req: &AwsRequest,
1833    ) -> Result<AwsResponse, AwsServiceError> {
1834        let b = parse(req)?;
1835        validate_common(&b)?;
1836        let mtype = req_str(&b, "AuthenticationMethodType")?.to_string();
1837        self.with_application(req, &b, |app| {
1838            app.auth_methods.remove(&mtype);
1839            ok(json!({}))
1840        })
1841    }
1842
1843    fn list_application_authentication_methods(
1844        &self,
1845        req: &AwsRequest,
1846    ) -> Result<AwsResponse, AwsServiceError> {
1847        let b = parse(req)?;
1848        validate_common(&b)?;
1849        let arn = req_str(&b, "ApplicationArn")?.to_string();
1850        let guard = self.state.read();
1851        let rows: Vec<Value> = self
1852            .application(&guard, &req.account_id, &arn)
1853            .map(|app| {
1854                app.auth_methods
1855                    .iter()
1856                    .map(|(t, m)| json!({ "AuthenticationMethodType": t, "AuthenticationMethod": m }))
1857                    .collect()
1858            })
1859            .unwrap_or_default();
1860        list_response("AuthenticationMethods", rows, &b)
1861    }
1862
1863    // ---------- application grants ----------
1864
1865    fn put_application_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1866        let b = parse(req)?;
1867        validate_common(&b)?;
1868        let gtype = req_str(&b, "GrantType")?.to_string();
1869        let grant = b
1870            .get("Grant")
1871            .cloned()
1872            .ok_or_else(|| validation("Grant must be specified."))?;
1873        self.with_application(req, &b, |app| {
1874            app.grants.insert(gtype, grant);
1875            ok(json!({}))
1876        })
1877    }
1878
1879    fn get_application_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1880        let b = parse(req)?;
1881        validate_common(&b)?;
1882        let arn = req_str(&b, "ApplicationArn")?.to_string();
1883        let gtype = req_str(&b, "GrantType")?.to_string();
1884        let guard = self.state.read();
1885        let app = self
1886            .application(&guard, &req.account_id, &arn)
1887            .ok_or_else(|| not_found("Application not found."))?;
1888        let grant = app
1889            .grants
1890            .get(&gtype)
1891            .ok_or_else(|| not_found("Grant not found."))?;
1892        ok(json!({ "Grant": grant }))
1893    }
1894
1895    fn delete_application_grant(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1896        let b = parse(req)?;
1897        validate_common(&b)?;
1898        let gtype = req_str(&b, "GrantType")?.to_string();
1899        self.with_application(req, &b, |app| {
1900            app.grants.remove(&gtype);
1901            ok(json!({}))
1902        })
1903    }
1904
1905    fn list_application_grants(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1906        let b = parse(req)?;
1907        validate_common(&b)?;
1908        let arn = req_str(&b, "ApplicationArn")?.to_string();
1909        let guard = self.state.read();
1910        let rows: Vec<Value> = self
1911            .application(&guard, &req.account_id, &arn)
1912            .map(|app| {
1913                app.grants
1914                    .iter()
1915                    .map(|(t, g)| json!({ "GrantType": t, "Grant": g }))
1916                    .collect()
1917            })
1918            .unwrap_or_default();
1919        list_response("Grants", rows, &b)
1920    }
1921
1922    // ---------- application session configuration ----------
1923
1924    fn get_application_session_configuration(
1925        &self,
1926        req: &AwsRequest,
1927    ) -> Result<AwsResponse, AwsServiceError> {
1928        let b = parse(req)?;
1929        validate_common(&b)?;
1930        let arn = req_str(&b, "ApplicationArn")?.to_string();
1931        let guard = self.state.read();
1932        let app = self
1933            .application(&guard, &req.account_id, &arn)
1934            .ok_or_else(|| not_found("Application not found."))?;
1935        let mut m = serde_json::Map::new();
1936        if let Some(status) = &app.session_status {
1937            m.insert(
1938                "UserBackgroundSessionApplicationStatus".into(),
1939                json!(status),
1940            );
1941        }
1942        ok(Value::Object(m))
1943    }
1944
1945    fn put_application_session_configuration(
1946        &self,
1947        req: &AwsRequest,
1948    ) -> Result<AwsResponse, AwsServiceError> {
1949        let b = parse(req)?;
1950        validate_common(&b)?;
1951        let status = b
1952            .get("UserBackgroundSessionApplicationStatus")
1953            .and_then(Value::as_str)
1954            .map(str::to_string);
1955        self.with_application(req, &b, |app| {
1956            app.session_status = status;
1957            ok(json!({}))
1958        })
1959    }
1960
1961    // ---------- application providers ----------
1962
1963    fn list_application_providers(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
1964        let b = parse(req)?;
1965        validate_common(&b)?;
1966        let rows: Vec<Value> = APPLICATION_PROVIDERS
1967            .iter()
1968            .map(|(arn, proto, display)| {
1969                json!({
1970                    "ApplicationProviderArn": arn,
1971                    "FederationProtocol": proto,
1972                    "DisplayData": { "DisplayName": display },
1973                })
1974            })
1975            .collect();
1976        list_response("ApplicationProviders", rows, &b)
1977    }
1978
1979    fn describe_application_provider(
1980        &self,
1981        req: &AwsRequest,
1982    ) -> Result<AwsResponse, AwsServiceError> {
1983        let b = parse(req)?;
1984        validate_common(&b)?;
1985        let arn = req_str(&b, "ApplicationProviderArn")?.to_string();
1986        let found = APPLICATION_PROVIDERS
1987            .iter()
1988            .find(|(a, _, _)| *a == arn)
1989            .ok_or_else(|| not_found("Application provider not found."))?;
1990        ok(json!({
1991            "ApplicationProviderArn": found.0,
1992            "FederationProtocol": found.1,
1993            "DisplayData": { "DisplayName": found.2 },
1994        }))
1995    }
1996
1997    // ---------- trusted token issuers ----------
1998
1999    fn create_trusted_token_issuer(
2000        &self,
2001        req: &AwsRequest,
2002    ) -> Result<AwsResponse, AwsServiceError> {
2003        let b = parse(req)?;
2004        validate_common(&b)?;
2005        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2006        let name = req_str(&b, "Name")?.to_string();
2007        check_len(&b, "Name", 1, 255)?;
2008        req_str(&b, "TrustedTokenIssuerType")?;
2009        // `CreateTrustedTokenIssuer` declares no ResourceNotFoundException, so
2010        // synthesize the parent instance's fragment when it's not on record.
2011        let guard = self.state.read();
2012        let ssoins = guard
2013            .get(&req.account_id)
2014            .and_then(|a| a.instances.get(&instance_arn))
2015            .map(|i| i.ssoins.clone())
2016            .unwrap_or_else(|| format!("ssoins-{}", hex16()));
2017        drop(guard);
2018        let arn = format!(
2019            "arn:aws:sso::{}:trustedTokenIssuer/{ssoins}/tti-{}",
2020            req.account_id,
2021            Uuid::new_v4()
2022        );
2023        let tti = StoredTrustedTokenIssuer {
2024            arn: arn.clone(),
2025            instance_arn,
2026            name: Some(name),
2027            issuer_type: b
2028                .get("TrustedTokenIssuerType")
2029                .and_then(Value::as_str)
2030                .unwrap_or("OIDC_JWT")
2031                .to_string(),
2032            config: b.get("TrustedTokenIssuerConfiguration").cloned(),
2033        };
2034        {
2035            let mut guard = self.state.write();
2036            let acct = guard.get_or_create(&req.account_id);
2037            acct.trusted_token_issuers.insert(arn.clone(), tti);
2038            store_tags(acct, &arn, &b);
2039        }
2040        ok(json!({ "TrustedTokenIssuerArn": arn }))
2041    }
2042
2043    fn describe_trusted_token_issuer(
2044        &self,
2045        req: &AwsRequest,
2046    ) -> Result<AwsResponse, AwsServiceError> {
2047        let b = parse(req)?;
2048        validate_common(&b)?;
2049        let arn = req_str(&b, "TrustedTokenIssuerArn")?.to_string();
2050        let guard = self.state.read();
2051        let tti = guard
2052            .get(&req.account_id)
2053            .and_then(|a| a.trusted_token_issuers.get(&arn))
2054            .ok_or_else(|| not_found("Trusted token issuer not found."))?;
2055        ok(build_tti_describe(tti))
2056    }
2057
2058    fn update_trusted_token_issuer(
2059        &self,
2060        req: &AwsRequest,
2061    ) -> Result<AwsResponse, AwsServiceError> {
2062        let b = parse(req)?;
2063        validate_common(&b)?;
2064        check_len(&b, "Name", 1, 255)?;
2065        let arn = req_str(&b, "TrustedTokenIssuerArn")?.to_string();
2066        let mut guard = self.state.write();
2067        let tti = guard
2068            .get_mut(&req.account_id)
2069            .and_then(|a| a.trusted_token_issuers.get_mut(&arn))
2070            .ok_or_else(|| not_found("Trusted token issuer not found."))?;
2071        if let Some(n) = b.get("Name").and_then(Value::as_str) {
2072            tti.name = Some(n.to_string());
2073        }
2074        if let Some(c) = b.get("TrustedTokenIssuerConfiguration") {
2075            // The update configuration omits the immutable `IssuerUrl`; merge so
2076            // the stored describe config keeps it instead of dropping it (which
2077            // would make Terraform see a perpetual replacement diff).
2078            match &mut tti.config {
2079                Some(existing) => deep_merge(existing, c),
2080                None => tti.config = Some(c.clone()),
2081            }
2082        }
2083        ok(json!({}))
2084    }
2085
2086    fn delete_trusted_token_issuer(
2087        &self,
2088        req: &AwsRequest,
2089    ) -> Result<AwsResponse, AwsServiceError> {
2090        let b = parse(req)?;
2091        validate_common(&b)?;
2092        let arn = req_str(&b, "TrustedTokenIssuerArn")?.to_string();
2093        if let Some(a) = self.state.write().get_mut(&req.account_id) {
2094            a.trusted_token_issuers.remove(&arn);
2095        }
2096        ok(json!({}))
2097    }
2098
2099    fn list_trusted_token_issuers(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2100        let b = parse(req)?;
2101        validate_common(&b)?;
2102        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2103        let guard = self.state.read();
2104        let rows: Vec<Value> = guard
2105            .get(&req.account_id)
2106            .map(|a| {
2107                a.trusted_token_issuers
2108                    .values()
2109                    .filter(|t| t.instance_arn == instance_arn)
2110                    .map(build_tti_metadata)
2111                    .collect()
2112            })
2113            .unwrap_or_default();
2114        list_response("TrustedTokenIssuers", rows, &b)
2115    }
2116
2117    // ---------- regions ----------
2118
2119    fn add_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2120        let b = parse(req)?;
2121        validate_common(&b)?;
2122        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2123        let region_name = req_str(&b, "RegionName")?.to_string();
2124        let mut guard = self.state.write();
2125        let acct = guard.get_or_create(&req.account_id);
2126        // `AddRegion` declares no ResourceNotFoundException, so materialize the
2127        // instance record if it isn't already present.
2128        let ssoins = instance_arn
2129            .rsplit('/')
2130            .next()
2131            .unwrap_or("ssoins-unknown")
2132            .to_string();
2133        let inst = acct
2134            .instances
2135            .entry(instance_arn.clone())
2136            .or_insert_with(|| StoredInstance {
2137                arn: instance_arn.clone(),
2138                ssoins,
2139                identity_store_id: format!("d-{}", hex10()),
2140                owner_account_id: req.account_id.clone(),
2141                name: None,
2142                status: "ACTIVE".into(),
2143                created_date: now(),
2144                regions: Vec::new(),
2145                primary_region: Some(req.region.clone()),
2146                attr_config: None,
2147                attr_config_status: None,
2148            });
2149        if !inst.regions.iter().any(|r| r.region_name == region_name) {
2150            let is_primary = inst.regions.is_empty();
2151            if is_primary {
2152                inst.primary_region = Some(region_name.clone());
2153            }
2154            inst.regions.push(StoredRegion {
2155                region_name,
2156                status: "ACTIVE".into(),
2157                added_date: now(),
2158                is_primary,
2159            });
2160        }
2161        ok(json!({ "Status": "ACTIVE" }))
2162    }
2163
2164    fn remove_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2165        let b = parse(req)?;
2166        validate_common(&b)?;
2167        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2168        let region_name = req_str(&b, "RegionName")?.to_string();
2169        if let Some(inst) = self
2170            .state
2171            .write()
2172            .get_mut(&req.account_id)
2173            .and_then(|a| a.instances.get_mut(&instance_arn))
2174        {
2175            inst.regions.retain(|r| r.region_name != region_name);
2176        }
2177        ok(json!({ "Status": "REMOVING" }))
2178    }
2179
2180    fn describe_region(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2181        let b = parse(req)?;
2182        validate_common(&b)?;
2183        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2184        let region_name = req_str(&b, "RegionName")?.to_string();
2185        let guard = self.state.read();
2186        let region = guard
2187            .get(&req.account_id)
2188            .and_then(|a| a.instances.get(&instance_arn))
2189            .and_then(|i| i.regions.iter().find(|r| r.region_name == region_name))
2190            .ok_or_else(|| not_found("Region not found."))?;
2191        ok(build_region(region))
2192    }
2193
2194    fn list_regions(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2195        let b = parse(req)?;
2196        validate_common(&b)?;
2197        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2198        let guard = self.state.read();
2199        let rows: Vec<Value> = guard
2200            .get(&req.account_id)
2201            .and_then(|a| a.instances.get(&instance_arn))
2202            .map(|i| i.regions.iter().map(build_region).collect())
2203            .unwrap_or_default();
2204        list_response("Regions", rows, &b)
2205    }
2206
2207    // ---------- access control attribute configuration ----------
2208
2209    fn create_attr_config(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2210        let b = parse(req)?;
2211        validate_common(&b)?;
2212        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2213        let config = b
2214            .get("InstanceAccessControlAttributeConfiguration")
2215            .cloned()
2216            .ok_or_else(|| {
2217                validation("InstanceAccessControlAttributeConfiguration must be specified.")
2218            })?;
2219        let mut guard = self.state.write();
2220        let inst = guard
2221            .get_mut(&req.account_id)
2222            .and_then(|a| a.instances.get_mut(&instance_arn))
2223            .ok_or_else(|| not_found("Instance not found."))?;
2224        inst.attr_config = Some(config);
2225        inst.attr_config_status = Some("ENABLED".into());
2226        ok(json!({}))
2227    }
2228
2229    fn describe_attr_config(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2230        let b = parse(req)?;
2231        validate_common(&b)?;
2232        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2233        let guard = self.state.read();
2234        let inst = guard
2235            .get(&req.account_id)
2236            .and_then(|a| a.instances.get(&instance_arn))
2237            .ok_or_else(|| not_found("Instance not found."))?;
2238        let config = inst
2239            .attr_config
2240            .clone()
2241            .ok_or_else(|| not_found("Access control attribute configuration not found."))?;
2242        ok(json!({
2243            "Status": inst.attr_config_status.clone().unwrap_or_else(|| "ENABLED".into()),
2244            "InstanceAccessControlAttributeConfiguration": config,
2245        }))
2246    }
2247
2248    fn update_attr_config(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2249        let b = parse(req)?;
2250        validate_common(&b)?;
2251        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2252        let config = b
2253            .get("InstanceAccessControlAttributeConfiguration")
2254            .cloned()
2255            .ok_or_else(|| {
2256                validation("InstanceAccessControlAttributeConfiguration must be specified.")
2257            })?;
2258        let mut guard = self.state.write();
2259        let inst = guard
2260            .get_mut(&req.account_id)
2261            .and_then(|a| a.instances.get_mut(&instance_arn))
2262            .ok_or_else(|| not_found("Instance not found."))?;
2263        inst.attr_config = Some(config);
2264        inst.attr_config_status = Some("ENABLED".into());
2265        ok(json!({}))
2266    }
2267
2268    fn delete_attr_config(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2269        let b = parse(req)?;
2270        validate_common(&b)?;
2271        let instance_arn = req_str(&b, "InstanceArn")?.to_string();
2272        if let Some(inst) = self
2273            .state
2274            .write()
2275            .get_mut(&req.account_id)
2276            .and_then(|a| a.instances.get_mut(&instance_arn))
2277        {
2278            inst.attr_config = None;
2279            inst.attr_config_status = None;
2280        }
2281        ok(json!({}))
2282    }
2283
2284    // ---------- tagging ----------
2285
2286    fn tag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2287        let b = parse(req)?;
2288        validate_common(&b)?;
2289        let resource_arn = req_str(&b, "ResourceArn")?.to_string();
2290        let tags = b
2291            .get("Tags")
2292            .and_then(Value::as_array)
2293            .ok_or_else(|| validation("Tags must be specified."))?;
2294        let mut guard = self.state.write();
2295        let entry = guard
2296            .get_or_create(&req.account_id)
2297            .tags
2298            .entry(resource_arn)
2299            .or_default();
2300        for tag in tags {
2301            if let (Some(k), Some(v)) = (
2302                tag.get("Key").and_then(Value::as_str),
2303                tag.get("Value").and_then(Value::as_str),
2304            ) {
2305                entry.insert(k.to_string(), v.to_string());
2306            }
2307        }
2308        ok(json!({}))
2309    }
2310
2311    fn untag_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2312        let b = parse(req)?;
2313        validate_common(&b)?;
2314        let resource_arn = req_str(&b, "ResourceArn")?.to_string();
2315        let keys = b
2316            .get("TagKeys")
2317            .and_then(Value::as_array)
2318            .ok_or_else(|| validation("TagKeys must be specified."))?;
2319        if let Some(entry) = self
2320            .state
2321            .write()
2322            .get_mut(&req.account_id)
2323            .and_then(|a| a.tags.get_mut(&resource_arn))
2324        {
2325            for k in keys {
2326                if let Some(k) = k.as_str() {
2327                    entry.remove(k);
2328                }
2329            }
2330        }
2331        ok(json!({}))
2332    }
2333
2334    fn list_tags_for_resource(&self, req: &AwsRequest) -> Result<AwsResponse, AwsServiceError> {
2335        let b = parse(req)?;
2336        validate_common(&b)?;
2337        let resource_arn = req_str(&b, "ResourceArn")?.to_string();
2338        let guard = self.state.read();
2339        let rows: Vec<Value> = guard
2340            .get(&req.account_id)
2341            .and_then(|a| a.tags.get(&resource_arn))
2342            .map(|m| {
2343                m.iter()
2344                    .map(|(k, v)| json!({ "Key": k, "Value": v }))
2345                    .collect()
2346            })
2347            .unwrap_or_default();
2348        list_response("Tags", rows, &b)
2349    }
2350}
2351
2352#[cfg(test)]
2353mod tests;