Skip to main content

winterbaume_workspacesweb/
state.rs

1use std::collections::HashMap;
2
3use chrono::Utc;
4
5use crate::types::*;
6
7#[derive(Debug, Default)]
8pub struct WorkspacesWebState {
9    pub portals: HashMap<String, Portal>,
10    pub browser_settings: HashMap<String, BrowserSettings>,
11    pub network_settings: HashMap<String, NetworkSettings>,
12    pub user_access_logging_settings: HashMap<String, UserAccessLoggingSettings>,
13    pub user_settings: HashMap<String, UserSettings>,
14    pub identity_providers: HashMap<String, IdentityProvider>,
15    pub ip_access_settings: HashMap<String, IpAccessSettings>,
16    pub trust_stores: HashMap<String, TrustStore>,
17    pub data_protection_settings: HashMap<String, DataProtectionSettings>,
18    pub session_loggers: HashMap<String, SessionLogger>,
19    pub sessions: HashMap<String, Session>,
20}
21
22#[derive(Debug, thiserror::Error)]
23pub enum WorkspacesWebError {
24    #[error("Resource {0} not found.")]
25    ResourceNotFound(String),
26    #[error("Session {0} not found.")]
27    SessionNotFound(String),
28}
29
30impl WorkspacesWebState {
31    // ── Portal ──────────────────────────────────────────────────────
32
33    pub fn create_portal(
34        &mut self,
35        display_name: Option<&str>,
36        account_id: &str,
37        region: &str,
38        tags: HashMap<String, String>,
39    ) -> Result<&Portal, WorkspacesWebError> {
40        let portal_id = uuid::Uuid::new_v4().to_string();
41        let portal_arn = format!("arn:aws:workspaces-web:{region}:{account_id}:portal/{portal_id}");
42        let portal_endpoint = format!("https://{portal_id}.workspaces-web.{region}.amazonaws.com");
43
44        let name = display_name.unwrap_or("").to_string();
45
46        let portal = Portal {
47            portal_arn: portal_arn.clone(),
48            portal_endpoint,
49            display_name: name,
50            portal_status: "Active".to_string(),
51            browser_type: "Chrome".to_string(),
52            renderer_type: "AppStream".to_string(),
53            creation_date: Utc::now(),
54            browser_settings_arn: None,
55            network_settings_arn: None,
56            user_access_logging_settings_arn: None,
57            user_settings_arn: None,
58            trust_store_arn: None,
59            ip_access_settings_arn: None,
60            data_protection_settings_arn: None,
61            session_logger_arn: None,
62            tags,
63        };
64
65        self.portals.insert(portal_arn.clone(), portal);
66        Ok(self.portals.get(&portal_arn).unwrap())
67    }
68
69    pub fn get_portal(&self, portal_arn: &str) -> Result<&Portal, WorkspacesWebError> {
70        self.portals
71            .get(portal_arn)
72            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))
73    }
74
75    pub fn delete_portal(&mut self, portal_arn: &str) -> Result<(), WorkspacesWebError> {
76        if self.portals.remove(portal_arn).is_none() {
77            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
78        }
79        Ok(())
80    }
81
82    pub fn list_portals(&self) -> Vec<&Portal> {
83        self.portals.values().collect()
84    }
85
86    // ── BrowserSettings ─────────────────────────────────────────────
87
88    pub fn create_browser_settings(
89        &mut self,
90        browser_policy: Option<&str>,
91        account_id: &str,
92        region: &str,
93        tags: HashMap<String, String>,
94    ) -> Result<&BrowserSettings, WorkspacesWebError> {
95        let id = uuid::Uuid::new_v4().to_string();
96        let arn = format!("arn:aws:workspaces-web:{region}:{account_id}:browserSettings/{id}");
97
98        let bs = BrowserSettings {
99            browser_settings_arn: arn.clone(),
100            browser_policy: browser_policy.map(|s| s.to_string()),
101            associated_portal_arns: Vec::new(),
102            tags,
103        };
104
105        self.browser_settings.insert(arn.clone(), bs);
106        Ok(self.browser_settings.get(&arn).unwrap())
107    }
108
109    pub fn get_browser_settings(&self, arn: &str) -> Result<&BrowserSettings, WorkspacesWebError> {
110        self.browser_settings
111            .get(arn)
112            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
113    }
114
115    pub fn delete_browser_settings(&mut self, arn: &str) -> Result<(), WorkspacesWebError> {
116        if self.browser_settings.remove(arn).is_none() {
117            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
118        }
119        Ok(())
120    }
121
122    pub fn list_browser_settings(&self) -> Vec<&BrowserSettings> {
123        self.browser_settings.values().collect()
124    }
125
126    // ── NetworkSettings ─────────────────────────────────────────────
127
128    pub fn create_network_settings(
129        &mut self,
130        vpc_id: &str,
131        subnet_ids: Vec<String>,
132        security_group_ids: Vec<String>,
133        account_id: &str,
134        region: &str,
135        tags: HashMap<String, String>,
136    ) -> Result<&NetworkSettings, WorkspacesWebError> {
137        let id = uuid::Uuid::new_v4().to_string();
138        let arn = format!("arn:aws:workspaces-web:{region}:{account_id}:networkSettings/{id}");
139
140        let ns = NetworkSettings {
141            network_settings_arn: arn.clone(),
142            vpc_id: vpc_id.to_string(),
143            subnet_ids,
144            security_group_ids,
145            associated_portal_arns: Vec::new(),
146            tags,
147        };
148
149        self.network_settings.insert(arn.clone(), ns);
150        Ok(self.network_settings.get(&arn).unwrap())
151    }
152
153    pub fn get_network_settings(&self, arn: &str) -> Result<&NetworkSettings, WorkspacesWebError> {
154        self.network_settings
155            .get(arn)
156            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
157    }
158
159    pub fn delete_network_settings(&mut self, arn: &str) -> Result<(), WorkspacesWebError> {
160        if self.network_settings.remove(arn).is_none() {
161            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
162        }
163        Ok(())
164    }
165
166    pub fn list_network_settings(&self) -> Vec<&NetworkSettings> {
167        self.network_settings.values().collect()
168    }
169
170    // ── UserAccessLoggingSettings ───────────────────────────────────
171
172    pub fn create_user_access_logging_settings(
173        &mut self,
174        kinesis_stream_arn: &str,
175        account_id: &str,
176        region: &str,
177        tags: HashMap<String, String>,
178    ) -> Result<&UserAccessLoggingSettings, WorkspacesWebError> {
179        let id = uuid::Uuid::new_v4().to_string();
180        let arn =
181            format!("arn:aws:workspaces-web:{region}:{account_id}:userAccessLoggingSettings/{id}");
182
183        let ual = UserAccessLoggingSettings {
184            user_access_logging_settings_arn: arn.clone(),
185            kinesis_stream_arn: kinesis_stream_arn.to_string(),
186            associated_portal_arns: Vec::new(),
187            tags,
188        };
189
190        self.user_access_logging_settings.insert(arn.clone(), ual);
191        Ok(self.user_access_logging_settings.get(&arn).unwrap())
192    }
193
194    pub fn get_user_access_logging_settings(
195        &self,
196        arn: &str,
197    ) -> Result<&UserAccessLoggingSettings, WorkspacesWebError> {
198        self.user_access_logging_settings
199            .get(arn)
200            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
201    }
202
203    pub fn delete_user_access_logging_settings(
204        &mut self,
205        arn: &str,
206    ) -> Result<(), WorkspacesWebError> {
207        if self.user_access_logging_settings.remove(arn).is_none() {
208            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
209        }
210        Ok(())
211    }
212
213    pub fn list_user_access_logging_settings(&self) -> Vec<&UserAccessLoggingSettings> {
214        self.user_access_logging_settings.values().collect()
215    }
216
217    // ── UserSettings ────────────────────────────────────────────────
218
219    pub fn create_user_settings(
220        &mut self,
221        copy_allowed: &str,
222        paste_allowed: &str,
223        download_allowed: &str,
224        upload_allowed: &str,
225        print_allowed: &str,
226        disconnect_timeout_in_minutes: Option<i32>,
227        idle_disconnect_timeout_in_minutes: Option<i32>,
228        account_id: &str,
229        region: &str,
230        tags: HashMap<String, String>,
231    ) -> Result<&UserSettings, WorkspacesWebError> {
232        let id = uuid::Uuid::new_v4().to_string();
233        let arn = format!("arn:aws:workspaces-web:{region}:{account_id}:userSettings/{id}");
234
235        let us = UserSettings {
236            user_settings_arn: arn.clone(),
237            copy_allowed: copy_allowed.to_string(),
238            paste_allowed: paste_allowed.to_string(),
239            download_allowed: download_allowed.to_string(),
240            upload_allowed: upload_allowed.to_string(),
241            print_allowed: print_allowed.to_string(),
242            disconnect_timeout_in_minutes,
243            idle_disconnect_timeout_in_minutes,
244            associated_portal_arns: Vec::new(),
245            tags,
246        };
247
248        self.user_settings.insert(arn.clone(), us);
249        Ok(self.user_settings.get(&arn).unwrap())
250    }
251
252    pub fn get_user_settings(&self, arn: &str) -> Result<&UserSettings, WorkspacesWebError> {
253        self.user_settings
254            .get(arn)
255            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
256    }
257
258    pub fn delete_user_settings(&mut self, arn: &str) -> Result<(), WorkspacesWebError> {
259        if self.user_settings.remove(arn).is_none() {
260            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
261        }
262        Ok(())
263    }
264
265    pub fn list_user_settings(&self) -> Vec<&UserSettings> {
266        self.user_settings.values().collect()
267    }
268
269    // ── Association helpers ─────────────────────────────────────────
270
271    pub fn associate_browser_settings(
272        &mut self,
273        portal_arn: &str,
274        browser_settings_arn: &str,
275    ) -> Result<(), WorkspacesWebError> {
276        // Validate both exist
277        if !self.portals.contains_key(portal_arn) {
278            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
279        }
280        if !self.browser_settings.contains_key(browser_settings_arn) {
281            return Err(WorkspacesWebError::ResourceNotFound(
282                browser_settings_arn.to_string(),
283            ));
284        }
285        self.portals
286            .get_mut(portal_arn)
287            .unwrap()
288            .browser_settings_arn = Some(browser_settings_arn.to_string());
289        let bs = self.browser_settings.get_mut(browser_settings_arn).unwrap();
290        if !bs.associated_portal_arns.contains(&portal_arn.to_string()) {
291            bs.associated_portal_arns.push(portal_arn.to_string());
292        }
293        Ok(())
294    }
295
296    pub fn associate_network_settings(
297        &mut self,
298        portal_arn: &str,
299        network_settings_arn: &str,
300    ) -> Result<(), WorkspacesWebError> {
301        if !self.portals.contains_key(portal_arn) {
302            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
303        }
304        if !self.network_settings.contains_key(network_settings_arn) {
305            return Err(WorkspacesWebError::ResourceNotFound(
306                network_settings_arn.to_string(),
307            ));
308        }
309        self.portals
310            .get_mut(portal_arn)
311            .unwrap()
312            .network_settings_arn = Some(network_settings_arn.to_string());
313        let ns = self.network_settings.get_mut(network_settings_arn).unwrap();
314        if !ns.associated_portal_arns.contains(&portal_arn.to_string()) {
315            ns.associated_portal_arns.push(portal_arn.to_string());
316        }
317        Ok(())
318    }
319
320    pub fn associate_user_access_logging_settings(
321        &mut self,
322        portal_arn: &str,
323        user_access_logging_settings_arn: &str,
324    ) -> Result<(), WorkspacesWebError> {
325        if !self.portals.contains_key(portal_arn) {
326            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
327        }
328        if !self
329            .user_access_logging_settings
330            .contains_key(user_access_logging_settings_arn)
331        {
332            return Err(WorkspacesWebError::ResourceNotFound(
333                user_access_logging_settings_arn.to_string(),
334            ));
335        }
336        self.portals
337            .get_mut(portal_arn)
338            .unwrap()
339            .user_access_logging_settings_arn = Some(user_access_logging_settings_arn.to_string());
340        let ual = self
341            .user_access_logging_settings
342            .get_mut(user_access_logging_settings_arn)
343            .unwrap();
344        if !ual.associated_portal_arns.contains(&portal_arn.to_string()) {
345            ual.associated_portal_arns.push(portal_arn.to_string());
346        }
347        Ok(())
348    }
349
350    pub fn associate_user_settings(
351        &mut self,
352        portal_arn: &str,
353        user_settings_arn: &str,
354    ) -> Result<(), WorkspacesWebError> {
355        if !self.portals.contains_key(portal_arn) {
356            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
357        }
358        if !self.user_settings.contains_key(user_settings_arn) {
359            return Err(WorkspacesWebError::ResourceNotFound(
360                user_settings_arn.to_string(),
361            ));
362        }
363        self.portals.get_mut(portal_arn).unwrap().user_settings_arn =
364            Some(user_settings_arn.to_string());
365        let us = self.user_settings.get_mut(user_settings_arn).unwrap();
366        if !us.associated_portal_arns.contains(&portal_arn.to_string()) {
367            us.associated_portal_arns.push(portal_arn.to_string());
368        }
369        Ok(())
370    }
371
372    // ── Portal update ────────────────────────────────────────────────
373
374    pub fn update_portal(
375        &mut self,
376        portal_arn: &str,
377        display_name: Option<&str>,
378    ) -> Result<&Portal, WorkspacesWebError> {
379        let portal = self
380            .portals
381            .get_mut(portal_arn)
382            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
383        if let Some(name) = display_name {
384            portal.display_name = name.to_string();
385        }
386        Ok(self.portals.get(portal_arn).unwrap())
387    }
388
389    // ── IdentityProvider ─────────────────────────────────────────────
390
391    pub fn create_identity_provider(
392        &mut self,
393        portal_arn: &str,
394        name: &str,
395        provider_type: &str,
396        details: HashMap<String, String>,
397        account_id: &str,
398        region: &str,
399    ) -> Result<&IdentityProvider, WorkspacesWebError> {
400        if !self.portals.contains_key(portal_arn) {
401            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
402        }
403        let id = uuid::Uuid::new_v4().to_string();
404        let arn = format!("arn:aws:workspaces-web:{region}:{account_id}:identityProvider/{id}");
405        let ip = IdentityProvider {
406            identity_provider_arn: arn.clone(),
407            portal_arn: portal_arn.to_string(),
408            identity_provider_name: name.to_string(),
409            identity_provider_type: provider_type.to_string(),
410            identity_provider_details: details,
411        };
412        self.identity_providers.insert(arn.clone(), ip);
413        Ok(self.identity_providers.get(&arn).unwrap())
414    }
415
416    pub fn get_identity_provider(
417        &self,
418        arn: &str,
419    ) -> Result<&IdentityProvider, WorkspacesWebError> {
420        self.identity_providers
421            .get(arn)
422            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
423    }
424
425    pub fn delete_identity_provider(&mut self, arn: &str) -> Result<(), WorkspacesWebError> {
426        if self.identity_providers.remove(arn).is_none() {
427            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
428        }
429        Ok(())
430    }
431
432    pub fn list_identity_providers(&self, portal_arn: &str) -> Vec<&IdentityProvider> {
433        self.identity_providers
434            .values()
435            .filter(|ip| ip.portal_arn == portal_arn)
436            .collect()
437    }
438
439    pub fn update_identity_provider(
440        &mut self,
441        arn: &str,
442        name: Option<&str>,
443        provider_type: Option<&str>,
444        details: Option<HashMap<String, String>>,
445    ) -> Result<&IdentityProvider, WorkspacesWebError> {
446        let ip = self
447            .identity_providers
448            .get_mut(arn)
449            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
450        if let Some(n) = name {
451            ip.identity_provider_name = n.to_string();
452        }
453        if let Some(t) = provider_type {
454            ip.identity_provider_type = t.to_string();
455        }
456        if let Some(d) = details {
457            ip.identity_provider_details = d;
458        }
459        Ok(self.identity_providers.get(arn).unwrap())
460    }
461
462    // ── IpAccessSettings ─────────────────────────────────────────────
463
464    pub fn create_ip_access_settings(
465        &mut self,
466        ip_rules: Vec<IpRule>,
467        display_name: Option<String>,
468        description: Option<String>,
469        account_id: &str,
470        region: &str,
471        tags: HashMap<String, String>,
472    ) -> Result<&IpAccessSettings, WorkspacesWebError> {
473        let id = uuid::Uuid::new_v4().to_string();
474        let arn = format!("arn:aws:workspaces-web:{region}:{account_id}:ipAccessSettings/{id}");
475        let ias = IpAccessSettings {
476            ip_access_settings_arn: arn.clone(),
477            display_name,
478            description,
479            ip_rules,
480            associated_portal_arns: Vec::new(),
481            creation_date: Utc::now(),
482            tags,
483        };
484        self.ip_access_settings.insert(arn.clone(), ias);
485        Ok(self.ip_access_settings.get(&arn).unwrap())
486    }
487
488    pub fn get_ip_access_settings(
489        &self,
490        arn: &str,
491    ) -> Result<&IpAccessSettings, WorkspacesWebError> {
492        self.ip_access_settings
493            .get(arn)
494            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
495    }
496
497    pub fn delete_ip_access_settings(&mut self, arn: &str) -> Result<(), WorkspacesWebError> {
498        if self.ip_access_settings.remove(arn).is_none() {
499            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
500        }
501        Ok(())
502    }
503
504    pub fn list_ip_access_settings(&self) -> Vec<&IpAccessSettings> {
505        self.ip_access_settings.values().collect()
506    }
507
508    pub fn update_ip_access_settings(
509        &mut self,
510        arn: &str,
511        ip_rules: Option<Vec<IpRule>>,
512        display_name: Option<String>,
513        description: Option<String>,
514    ) -> Result<&IpAccessSettings, WorkspacesWebError> {
515        let ias = self
516            .ip_access_settings
517            .get_mut(arn)
518            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
519        if let Some(rules) = ip_rules {
520            ias.ip_rules = rules;
521        }
522        if display_name.is_some() {
523            ias.display_name = display_name;
524        }
525        if description.is_some() {
526            ias.description = description;
527        }
528        Ok(self.ip_access_settings.get(arn).unwrap())
529    }
530
531    pub fn associate_ip_access_settings(
532        &mut self,
533        portal_arn: &str,
534        ip_access_settings_arn: &str,
535    ) -> Result<(), WorkspacesWebError> {
536        if !self.portals.contains_key(portal_arn) {
537            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
538        }
539        if !self.ip_access_settings.contains_key(ip_access_settings_arn) {
540            return Err(WorkspacesWebError::ResourceNotFound(
541                ip_access_settings_arn.to_string(),
542            ));
543        }
544        self.portals
545            .get_mut(portal_arn)
546            .unwrap()
547            .ip_access_settings_arn = Some(ip_access_settings_arn.to_string());
548        let ias = self
549            .ip_access_settings
550            .get_mut(ip_access_settings_arn)
551            .unwrap();
552        if !ias.associated_portal_arns.contains(&portal_arn.to_string()) {
553            ias.associated_portal_arns.push(portal_arn.to_string());
554        }
555        Ok(())
556    }
557
558    pub fn disassociate_ip_access_settings(
559        &mut self,
560        portal_arn: &str,
561    ) -> Result<(), WorkspacesWebError> {
562        let portal = self
563            .portals
564            .get_mut(portal_arn)
565            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
566        portal.ip_access_settings_arn = None;
567        Ok(())
568    }
569
570    // ── TrustStore ───────────────────────────────────────────────────
571
572    pub fn create_trust_store(
573        &mut self,
574        certificate_list: Vec<String>,
575        account_id: &str,
576        region: &str,
577        tags: HashMap<String, String>,
578    ) -> Result<&TrustStore, WorkspacesWebError> {
579        let id = uuid::Uuid::new_v4().to_string();
580        let arn = format!("arn:aws:workspaces-web:{region}:{account_id}:trustStore/{id}");
581        let certs: Vec<TrustStoreCertificate> = certificate_list
582            .into_iter()
583            .enumerate()
584            .map(|(i, body)| TrustStoreCertificate {
585                thumbprint: format!("thumb{i:04}"),
586                body,
587                issuer: None,
588                subject: None,
589            })
590            .collect();
591        let ts = TrustStore {
592            trust_store_arn: arn.clone(),
593            certificate_list: certs,
594            associated_portal_arns: Vec::new(),
595            tags,
596        };
597        self.trust_stores.insert(arn.clone(), ts);
598        Ok(self.trust_stores.get(&arn).unwrap())
599    }
600
601    pub fn get_trust_store(&self, arn: &str) -> Result<&TrustStore, WorkspacesWebError> {
602        self.trust_stores
603            .get(arn)
604            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
605    }
606
607    pub fn delete_trust_store(&mut self, arn: &str) -> Result<(), WorkspacesWebError> {
608        if self.trust_stores.remove(arn).is_none() {
609            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
610        }
611        Ok(())
612    }
613
614    pub fn list_trust_stores(&self) -> Vec<&TrustStore> {
615        self.trust_stores.values().collect()
616    }
617
618    pub fn update_trust_store(
619        &mut self,
620        arn: &str,
621        certs_to_add: Vec<String>,
622        thumbprints_to_delete: Vec<String>,
623    ) -> Result<&TrustStore, WorkspacesWebError> {
624        let ts = self
625            .trust_stores
626            .get_mut(arn)
627            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
628        ts.certificate_list
629            .retain(|c| !thumbprints_to_delete.contains(&c.thumbprint));
630        let existing_count = ts.certificate_list.len();
631        for (i, body) in certs_to_add.into_iter().enumerate() {
632            ts.certificate_list.push(TrustStoreCertificate {
633                thumbprint: format!("thumb{:04}", existing_count + i),
634                body,
635                issuer: None,
636                subject: None,
637            });
638        }
639        Ok(self.trust_stores.get(arn).unwrap())
640    }
641
642    pub fn associate_trust_store(
643        &mut self,
644        portal_arn: &str,
645        trust_store_arn: &str,
646    ) -> Result<(), WorkspacesWebError> {
647        if !self.portals.contains_key(portal_arn) {
648            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
649        }
650        if !self.trust_stores.contains_key(trust_store_arn) {
651            return Err(WorkspacesWebError::ResourceNotFound(
652                trust_store_arn.to_string(),
653            ));
654        }
655        self.portals.get_mut(portal_arn).unwrap().trust_store_arn =
656            Some(trust_store_arn.to_string());
657        let ts = self.trust_stores.get_mut(trust_store_arn).unwrap();
658        if !ts.associated_portal_arns.contains(&portal_arn.to_string()) {
659            ts.associated_portal_arns.push(portal_arn.to_string());
660        }
661        Ok(())
662    }
663
664    pub fn disassociate_trust_store(&mut self, portal_arn: &str) -> Result<(), WorkspacesWebError> {
665        let portal = self
666            .portals
667            .get_mut(portal_arn)
668            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
669        portal.trust_store_arn = None;
670        Ok(())
671    }
672
673    // ── DataProtectionSettings ───────────────────────────────────────
674
675    pub fn create_data_protection_settings(
676        &mut self,
677        display_name: Option<String>,
678        description: Option<String>,
679        account_id: &str,
680        region: &str,
681        tags: HashMap<String, String>,
682    ) -> Result<&DataProtectionSettings, WorkspacesWebError> {
683        let id = uuid::Uuid::new_v4().to_string();
684        let arn =
685            format!("arn:aws:workspaces-web:{region}:{account_id}:dataProtectionSettings/{id}");
686        let dps = DataProtectionSettings {
687            data_protection_settings_arn: arn.clone(),
688            display_name,
689            description,
690            associated_portal_arns: Vec::new(),
691            creation_date: Utc::now(),
692            tags,
693        };
694        self.data_protection_settings.insert(arn.clone(), dps);
695        Ok(self.data_protection_settings.get(&arn).unwrap())
696    }
697
698    pub fn get_data_protection_settings(
699        &self,
700        arn: &str,
701    ) -> Result<&DataProtectionSettings, WorkspacesWebError> {
702        self.data_protection_settings
703            .get(arn)
704            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
705    }
706
707    pub fn delete_data_protection_settings(&mut self, arn: &str) -> Result<(), WorkspacesWebError> {
708        if self.data_protection_settings.remove(arn).is_none() {
709            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
710        }
711        Ok(())
712    }
713
714    pub fn list_data_protection_settings(&self) -> Vec<&DataProtectionSettings> {
715        self.data_protection_settings.values().collect()
716    }
717
718    pub fn update_data_protection_settings(
719        &mut self,
720        arn: &str,
721        display_name: Option<String>,
722        description: Option<String>,
723    ) -> Result<&DataProtectionSettings, WorkspacesWebError> {
724        let dps = self
725            .data_protection_settings
726            .get_mut(arn)
727            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
728        if display_name.is_some() {
729            dps.display_name = display_name;
730        }
731        if description.is_some() {
732            dps.description = description;
733        }
734        Ok(self.data_protection_settings.get(arn).unwrap())
735    }
736
737    pub fn associate_data_protection_settings(
738        &mut self,
739        portal_arn: &str,
740        data_protection_settings_arn: &str,
741    ) -> Result<(), WorkspacesWebError> {
742        if !self.portals.contains_key(portal_arn) {
743            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
744        }
745        if !self
746            .data_protection_settings
747            .contains_key(data_protection_settings_arn)
748        {
749            return Err(WorkspacesWebError::ResourceNotFound(
750                data_protection_settings_arn.to_string(),
751            ));
752        }
753        self.portals
754            .get_mut(portal_arn)
755            .unwrap()
756            .data_protection_settings_arn = Some(data_protection_settings_arn.to_string());
757        let dps = self
758            .data_protection_settings
759            .get_mut(data_protection_settings_arn)
760            .unwrap();
761        if !dps.associated_portal_arns.contains(&portal_arn.to_string()) {
762            dps.associated_portal_arns.push(portal_arn.to_string());
763        }
764        Ok(())
765    }
766
767    pub fn disassociate_data_protection_settings(
768        &mut self,
769        portal_arn: &str,
770    ) -> Result<(), WorkspacesWebError> {
771        let portal = self
772            .portals
773            .get_mut(portal_arn)
774            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
775        portal.data_protection_settings_arn = None;
776        Ok(())
777    }
778
779    // ── SessionLogger ────────────────────────────────────────────────
780
781    pub fn create_session_logger(
782        &mut self,
783        display_name: Option<String>,
784        account_id: &str,
785        region: &str,
786        tags: HashMap<String, String>,
787    ) -> Result<&SessionLogger, WorkspacesWebError> {
788        let id = uuid::Uuid::new_v4().to_string();
789        let arn = format!("arn:aws:workspaces-web:{region}:{account_id}:sessionLogger/{id}");
790        let sl = SessionLogger {
791            session_logger_arn: arn.clone(),
792            display_name,
793            associated_portal_arns: Vec::new(),
794            creation_date: Utc::now(),
795            tags,
796        };
797        self.session_loggers.insert(arn.clone(), sl);
798        Ok(self.session_loggers.get(&arn).unwrap())
799    }
800
801    pub fn get_session_logger(&self, arn: &str) -> Result<&SessionLogger, WorkspacesWebError> {
802        self.session_loggers
803            .get(arn)
804            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))
805    }
806
807    pub fn delete_session_logger(&mut self, arn: &str) -> Result<(), WorkspacesWebError> {
808        if self.session_loggers.remove(arn).is_none() {
809            return Err(WorkspacesWebError::ResourceNotFound(arn.to_string()));
810        }
811        Ok(())
812    }
813
814    pub fn list_session_loggers(&self) -> Vec<&SessionLogger> {
815        self.session_loggers.values().collect()
816    }
817
818    pub fn update_session_logger(
819        &mut self,
820        arn: &str,
821        display_name: Option<String>,
822    ) -> Result<&SessionLogger, WorkspacesWebError> {
823        let sl = self
824            .session_loggers
825            .get_mut(arn)
826            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
827        if display_name.is_some() {
828            sl.display_name = display_name;
829        }
830        Ok(self.session_loggers.get(arn).unwrap())
831    }
832
833    pub fn associate_session_logger(
834        &mut self,
835        portal_arn: &str,
836        session_logger_arn: &str,
837    ) -> Result<(), WorkspacesWebError> {
838        if !self.portals.contains_key(portal_arn) {
839            return Err(WorkspacesWebError::ResourceNotFound(portal_arn.to_string()));
840        }
841        if !self.session_loggers.contains_key(session_logger_arn) {
842            return Err(WorkspacesWebError::ResourceNotFound(
843                session_logger_arn.to_string(),
844            ));
845        }
846        self.portals.get_mut(portal_arn).unwrap().session_logger_arn =
847            Some(session_logger_arn.to_string());
848        let sl = self.session_loggers.get_mut(session_logger_arn).unwrap();
849        if !sl.associated_portal_arns.contains(&portal_arn.to_string()) {
850            sl.associated_portal_arns.push(portal_arn.to_string());
851        }
852        Ok(())
853    }
854
855    pub fn disassociate_session_logger(
856        &mut self,
857        portal_arn: &str,
858    ) -> Result<(), WorkspacesWebError> {
859        let portal = self
860            .portals
861            .get_mut(portal_arn)
862            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
863        portal.session_logger_arn = None;
864        Ok(())
865    }
866
867    // ── Sessions ─────────────────────────────────────────────────────
868
869    pub fn get_session(
870        &self,
871        portal_id: &str,
872        session_id: &str,
873    ) -> Result<&Session, WorkspacesWebError> {
874        self.sessions
875            .get(session_id)
876            .filter(|s| s.portal_id == portal_id)
877            .ok_or_else(|| WorkspacesWebError::SessionNotFound(session_id.to_string()))
878    }
879
880    pub fn list_sessions(&self, portal_id: &str) -> Vec<&Session> {
881        self.sessions
882            .values()
883            .filter(|s| s.portal_id == portal_id)
884            .collect()
885    }
886
887    pub fn expire_session(
888        &mut self,
889        portal_id: &str,
890        session_id: &str,
891    ) -> Result<(), WorkspacesWebError> {
892        let session = self
893            .sessions
894            .get_mut(session_id)
895            .filter(|s| s.portal_id == portal_id)
896            .ok_or_else(|| WorkspacesWebError::SessionNotFound(session_id.to_string()))?;
897        session.status = "Terminated".to_string();
898        session.end_time = Some(Utc::now());
899        Ok(())
900    }
901
902    // ── Disassociate browser/network/ual/user settings ───────────────
903
904    pub fn disassociate_browser_settings(
905        &mut self,
906        portal_arn: &str,
907    ) -> Result<(), WorkspacesWebError> {
908        let portal = self
909            .portals
910            .get_mut(portal_arn)
911            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
912        portal.browser_settings_arn = None;
913        Ok(())
914    }
915
916    pub fn disassociate_network_settings(
917        &mut self,
918        portal_arn: &str,
919    ) -> Result<(), WorkspacesWebError> {
920        let portal = self
921            .portals
922            .get_mut(portal_arn)
923            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
924        portal.network_settings_arn = None;
925        Ok(())
926    }
927
928    pub fn disassociate_user_access_logging_settings(
929        &mut self,
930        portal_arn: &str,
931    ) -> Result<(), WorkspacesWebError> {
932        let portal = self
933            .portals
934            .get_mut(portal_arn)
935            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
936        portal.user_access_logging_settings_arn = None;
937        Ok(())
938    }
939
940    pub fn disassociate_user_settings(
941        &mut self,
942        portal_arn: &str,
943    ) -> Result<(), WorkspacesWebError> {
944        let portal = self
945            .portals
946            .get_mut(portal_arn)
947            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(portal_arn.to_string()))?;
948        portal.user_settings_arn = None;
949        Ok(())
950    }
951
952    // ── Update browser/network/ual/user settings ─────────────────────
953
954    pub fn update_browser_settings(
955        &mut self,
956        arn: &str,
957        browser_policy: Option<String>,
958    ) -> Result<&BrowserSettings, WorkspacesWebError> {
959        let bs = self
960            .browser_settings
961            .get_mut(arn)
962            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
963        if let Some(p) = browser_policy {
964            bs.browser_policy = Some(p);
965        }
966        Ok(self.browser_settings.get(arn).unwrap())
967    }
968
969    pub fn update_network_settings(
970        &mut self,
971        arn: &str,
972        vpc_id: Option<String>,
973        subnet_ids: Option<Vec<String>>,
974        security_group_ids: Option<Vec<String>>,
975    ) -> Result<&NetworkSettings, WorkspacesWebError> {
976        let ns = self
977            .network_settings
978            .get_mut(arn)
979            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
980        if let Some(v) = vpc_id {
981            ns.vpc_id = v;
982        }
983        if let Some(s) = subnet_ids {
984            ns.subnet_ids = s;
985        }
986        if let Some(sg) = security_group_ids {
987            ns.security_group_ids = sg;
988        }
989        Ok(self.network_settings.get(arn).unwrap())
990    }
991
992    pub fn update_user_access_logging_settings(
993        &mut self,
994        arn: &str,
995        kinesis_stream_arn: Option<String>,
996    ) -> Result<&UserAccessLoggingSettings, WorkspacesWebError> {
997        let ual = self
998            .user_access_logging_settings
999            .get_mut(arn)
1000            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
1001        if let Some(k) = kinesis_stream_arn {
1002            ual.kinesis_stream_arn = k;
1003        }
1004        Ok(self.user_access_logging_settings.get(arn).unwrap())
1005    }
1006
1007    pub fn update_user_settings(
1008        &mut self,
1009        arn: &str,
1010        copy_allowed: Option<String>,
1011        paste_allowed: Option<String>,
1012        download_allowed: Option<String>,
1013        upload_allowed: Option<String>,
1014        print_allowed: Option<String>,
1015        disconnect_timeout: Option<i32>,
1016        idle_disconnect_timeout: Option<i32>,
1017    ) -> Result<&UserSettings, WorkspacesWebError> {
1018        let us = self
1019            .user_settings
1020            .get_mut(arn)
1021            .ok_or_else(|| WorkspacesWebError::ResourceNotFound(arn.to_string()))?;
1022        if let Some(v) = copy_allowed {
1023            us.copy_allowed = v;
1024        }
1025        if let Some(v) = paste_allowed {
1026            us.paste_allowed = v;
1027        }
1028        if let Some(v) = download_allowed {
1029            us.download_allowed = v;
1030        }
1031        if let Some(v) = upload_allowed {
1032            us.upload_allowed = v;
1033        }
1034        if let Some(v) = print_allowed {
1035            us.print_allowed = v;
1036        }
1037        if let Some(v) = disconnect_timeout {
1038            us.disconnect_timeout_in_minutes = Some(v);
1039        }
1040        if let Some(v) = idle_disconnect_timeout {
1041            us.idle_disconnect_timeout_in_minutes = Some(v);
1042        }
1043        Ok(self.user_settings.get(arn).unwrap())
1044    }
1045
1046    // ── Tag helpers ─────────────────────────────────────────────────
1047
1048    pub fn get_tags_for_resource(
1049        &self,
1050        resource_arn: &str,
1051    ) -> Result<&HashMap<String, String>, WorkspacesWebError> {
1052        if let Some(p) = self.portals.get(resource_arn) {
1053            return Ok(&p.tags);
1054        }
1055        if let Some(bs) = self.browser_settings.get(resource_arn) {
1056            return Ok(&bs.tags);
1057        }
1058        if let Some(ns) = self.network_settings.get(resource_arn) {
1059            return Ok(&ns.tags);
1060        }
1061        if let Some(ual) = self.user_access_logging_settings.get(resource_arn) {
1062            return Ok(&ual.tags);
1063        }
1064        if let Some(us) = self.user_settings.get(resource_arn) {
1065            return Ok(&us.tags);
1066        }
1067        if let Some(ias) = self.ip_access_settings.get(resource_arn) {
1068            return Ok(&ias.tags);
1069        }
1070        if let Some(ts) = self.trust_stores.get(resource_arn) {
1071            return Ok(&ts.tags);
1072        }
1073        if let Some(dps) = self.data_protection_settings.get(resource_arn) {
1074            return Ok(&dps.tags);
1075        }
1076        if let Some(sl) = self.session_loggers.get(resource_arn) {
1077            return Ok(&sl.tags);
1078        }
1079        Err(WorkspacesWebError::ResourceNotFound(
1080            resource_arn.to_string(),
1081        ))
1082    }
1083
1084    pub fn tag_resource(
1085        &mut self,
1086        resource_arn: &str,
1087        tags: HashMap<String, String>,
1088    ) -> Result<(), WorkspacesWebError> {
1089        let tag_map = self.get_tags_mut(resource_arn)?;
1090        for (k, v) in tags {
1091            tag_map.insert(k, v);
1092        }
1093        Ok(())
1094    }
1095
1096    pub fn untag_resource(
1097        &mut self,
1098        resource_arn: &str,
1099        tag_keys: &[String],
1100    ) -> Result<(), WorkspacesWebError> {
1101        let tag_map = self.get_tags_mut(resource_arn)?;
1102        for key in tag_keys {
1103            tag_map.remove(key);
1104        }
1105        Ok(())
1106    }
1107
1108    fn get_tags_mut(
1109        &mut self,
1110        resource_arn: &str,
1111    ) -> Result<&mut HashMap<String, String>, WorkspacesWebError> {
1112        if self.portals.contains_key(resource_arn) {
1113            return Ok(&mut self.portals.get_mut(resource_arn).unwrap().tags);
1114        }
1115        if self.browser_settings.contains_key(resource_arn) {
1116            return Ok(&mut self.browser_settings.get_mut(resource_arn).unwrap().tags);
1117        }
1118        if self.network_settings.contains_key(resource_arn) {
1119            return Ok(&mut self.network_settings.get_mut(resource_arn).unwrap().tags);
1120        }
1121        if self.user_access_logging_settings.contains_key(resource_arn) {
1122            return Ok(&mut self
1123                .user_access_logging_settings
1124                .get_mut(resource_arn)
1125                .unwrap()
1126                .tags);
1127        }
1128        if self.user_settings.contains_key(resource_arn) {
1129            return Ok(&mut self.user_settings.get_mut(resource_arn).unwrap().tags);
1130        }
1131        if self.ip_access_settings.contains_key(resource_arn) {
1132            return Ok(&mut self.ip_access_settings.get_mut(resource_arn).unwrap().tags);
1133        }
1134        if self.trust_stores.contains_key(resource_arn) {
1135            return Ok(&mut self.trust_stores.get_mut(resource_arn).unwrap().tags);
1136        }
1137        if self.data_protection_settings.contains_key(resource_arn) {
1138            return Ok(&mut self
1139                .data_protection_settings
1140                .get_mut(resource_arn)
1141                .unwrap()
1142                .tags);
1143        }
1144        if self.session_loggers.contains_key(resource_arn) {
1145            return Ok(&mut self.session_loggers.get_mut(resource_arn).unwrap().tags);
1146        }
1147        Err(WorkspacesWebError::ResourceNotFound(
1148            resource_arn.to_string(),
1149        ))
1150    }
1151}