Skip to main content

winterbaume_workspacesweb/
views.rs

1//! Serde-compatible view types for WorkSpaces Web state snapshots.
2
3use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6use winterbaume_core::{StateChangeNotifier, StateViewError, StatefulService};
7
8use crate::handlers::WorkspacesWebService;
9use crate::state::WorkspacesWebState;
10use crate::types::{
11    BrowserSettings, DataProtectionSettings, IdentityProvider, IpAccessSettings, IpRule,
12    NetworkSettings, Portal, Session, SessionLogger, TrustStore, TrustStoreCertificate,
13    UserAccessLoggingSettings, UserSettings,
14};
15
16/// Serializable view of the entire WorkSpaces Web state for one account/region.
17#[derive(Debug, Clone, Serialize, Deserialize, Default)]
18pub struct WorkspacesWebStateView {
19    #[serde(default)]
20    pub portals: HashMap<String, PortalView>,
21    #[serde(default)]
22    pub browser_settings: HashMap<String, BrowserSettingsView>,
23    #[serde(default)]
24    pub network_settings: HashMap<String, NetworkSettingsView>,
25    #[serde(default)]
26    pub user_access_logging_settings: HashMap<String, UserAccessLoggingSettingsView>,
27    #[serde(default)]
28    pub user_settings: HashMap<String, UserSettingsView>,
29    #[serde(default)]
30    pub identity_providers: HashMap<String, IdentityProviderView>,
31    #[serde(default)]
32    pub ip_access_settings: HashMap<String, IpAccessSettingsView>,
33    #[serde(default)]
34    pub trust_stores: HashMap<String, TrustStoreView>,
35    #[serde(default)]
36    pub data_protection_settings: HashMap<String, DataProtectionSettingsView>,
37    #[serde(default)]
38    pub session_loggers: HashMap<String, SessionLoggerView>,
39    #[serde(default)]
40    pub sessions: HashMap<String, SessionView>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct PortalView {
45    pub portal_arn: String,
46    pub portal_endpoint: String,
47    pub display_name: String,
48    pub portal_status: String,
49    pub browser_type: String,
50    pub renderer_type: String,
51    pub creation_date: String,
52    pub browser_settings_arn: Option<String>,
53    pub network_settings_arn: Option<String>,
54    pub user_access_logging_settings_arn: Option<String>,
55    pub user_settings_arn: Option<String>,
56    #[serde(default)]
57    pub trust_store_arn: Option<String>,
58    #[serde(default)]
59    pub ip_access_settings_arn: Option<String>,
60    #[serde(default)]
61    pub data_protection_settings_arn: Option<String>,
62    #[serde(default)]
63    pub session_logger_arn: Option<String>,
64    #[serde(default)]
65    pub tags: HashMap<String, String>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct BrowserSettingsView {
70    pub browser_settings_arn: String,
71    pub browser_policy: Option<String>,
72    #[serde(default)]
73    pub associated_portal_arns: Vec<String>,
74    #[serde(default)]
75    pub tags: HashMap<String, String>,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct NetworkSettingsView {
80    pub network_settings_arn: String,
81    pub vpc_id: String,
82    #[serde(default)]
83    pub subnet_ids: Vec<String>,
84    #[serde(default)]
85    pub security_group_ids: Vec<String>,
86    #[serde(default)]
87    pub associated_portal_arns: Vec<String>,
88    #[serde(default)]
89    pub tags: HashMap<String, String>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct UserAccessLoggingSettingsView {
94    pub user_access_logging_settings_arn: String,
95    pub kinesis_stream_arn: String,
96    #[serde(default)]
97    pub associated_portal_arns: Vec<String>,
98    #[serde(default)]
99    pub tags: HashMap<String, String>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct UserSettingsView {
104    pub user_settings_arn: String,
105    pub copy_allowed: String,
106    pub paste_allowed: String,
107    pub download_allowed: String,
108    pub upload_allowed: String,
109    pub print_allowed: String,
110    pub disconnect_timeout_in_minutes: Option<i32>,
111    pub idle_disconnect_timeout_in_minutes: Option<i32>,
112    #[serde(default)]
113    pub associated_portal_arns: Vec<String>,
114    #[serde(default)]
115    pub tags: HashMap<String, String>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct IdentityProviderView {
120    pub identity_provider_arn: String,
121    pub portal_arn: String,
122    pub identity_provider_name: String,
123    pub identity_provider_type: String,
124    #[serde(default)]
125    pub identity_provider_details: HashMap<String, String>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct IpRuleView {
130    pub ip_range: String,
131    pub description: Option<String>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct IpAccessSettingsView {
136    pub ip_access_settings_arn: String,
137    pub display_name: Option<String>,
138    pub description: Option<String>,
139    #[serde(default)]
140    pub ip_rules: Vec<IpRuleView>,
141    #[serde(default)]
142    pub associated_portal_arns: Vec<String>,
143    pub creation_date: String,
144    #[serde(default)]
145    pub tags: HashMap<String, String>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct TrustStoreCertificateView {
150    pub thumbprint: String,
151    pub body: String,
152    pub issuer: Option<String>,
153    pub subject: Option<String>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct TrustStoreView {
158    pub trust_store_arn: String,
159    #[serde(default)]
160    pub certificate_list: Vec<TrustStoreCertificateView>,
161    #[serde(default)]
162    pub associated_portal_arns: Vec<String>,
163    #[serde(default)]
164    pub tags: HashMap<String, String>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
168pub struct DataProtectionSettingsView {
169    pub data_protection_settings_arn: String,
170    pub display_name: Option<String>,
171    pub description: Option<String>,
172    #[serde(default)]
173    pub associated_portal_arns: Vec<String>,
174    pub creation_date: String,
175    #[serde(default)]
176    pub tags: HashMap<String, String>,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct SessionLoggerView {
181    pub session_logger_arn: String,
182    pub display_name: Option<String>,
183    #[serde(default)]
184    pub associated_portal_arns: Vec<String>,
185    pub creation_date: String,
186    #[serde(default)]
187    pub tags: HashMap<String, String>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct SessionView {
192    pub session_id: String,
193    pub portal_id: String,
194    pub username: Option<String>,
195    pub status: String,
196    pub start_time: String,
197    pub end_time: Option<String>,
198    #[serde(default)]
199    pub client_ip_addresses: Vec<String>,
200}
201
202// --- From internal types to view types ---
203
204impl From<&WorkspacesWebState> for WorkspacesWebStateView {
205    fn from(state: &WorkspacesWebState) -> Self {
206        WorkspacesWebStateView {
207            portals: state
208                .portals
209                .iter()
210                .map(|(k, v)| {
211                    (
212                        k.clone(),
213                        PortalView {
214                            portal_arn: v.portal_arn.clone(),
215                            portal_endpoint: v.portal_endpoint.clone(),
216                            display_name: v.display_name.clone(),
217                            portal_status: v.portal_status.clone(),
218                            browser_type: v.browser_type.clone(),
219                            renderer_type: v.renderer_type.clone(),
220                            creation_date: v.creation_date.to_rfc3339(),
221                            browser_settings_arn: v.browser_settings_arn.clone(),
222                            network_settings_arn: v.network_settings_arn.clone(),
223                            user_access_logging_settings_arn: v
224                                .user_access_logging_settings_arn
225                                .clone(),
226                            user_settings_arn: v.user_settings_arn.clone(),
227                            trust_store_arn: v.trust_store_arn.clone(),
228                            ip_access_settings_arn: v.ip_access_settings_arn.clone(),
229                            data_protection_settings_arn: v.data_protection_settings_arn.clone(),
230                            session_logger_arn: v.session_logger_arn.clone(),
231                            tags: v.tags.clone(),
232                        },
233                    )
234                })
235                .collect(),
236            browser_settings: state
237                .browser_settings
238                .iter()
239                .map(|(k, v)| {
240                    (
241                        k.clone(),
242                        BrowserSettingsView {
243                            browser_settings_arn: v.browser_settings_arn.clone(),
244                            browser_policy: v.browser_policy.clone(),
245                            associated_portal_arns: v.associated_portal_arns.clone(),
246                            tags: v.tags.clone(),
247                        },
248                    )
249                })
250                .collect(),
251            network_settings: state
252                .network_settings
253                .iter()
254                .map(|(k, v)| {
255                    (
256                        k.clone(),
257                        NetworkSettingsView {
258                            network_settings_arn: v.network_settings_arn.clone(),
259                            vpc_id: v.vpc_id.clone(),
260                            subnet_ids: v.subnet_ids.clone(),
261                            security_group_ids: v.security_group_ids.clone(),
262                            associated_portal_arns: v.associated_portal_arns.clone(),
263                            tags: v.tags.clone(),
264                        },
265                    )
266                })
267                .collect(),
268            user_access_logging_settings: state
269                .user_access_logging_settings
270                .iter()
271                .map(|(k, v)| {
272                    (
273                        k.clone(),
274                        UserAccessLoggingSettingsView {
275                            user_access_logging_settings_arn: v
276                                .user_access_logging_settings_arn
277                                .clone(),
278                            kinesis_stream_arn: v.kinesis_stream_arn.clone(),
279                            associated_portal_arns: v.associated_portal_arns.clone(),
280                            tags: v.tags.clone(),
281                        },
282                    )
283                })
284                .collect(),
285            user_settings: state
286                .user_settings
287                .iter()
288                .map(|(k, v)| {
289                    (
290                        k.clone(),
291                        UserSettingsView {
292                            user_settings_arn: v.user_settings_arn.clone(),
293                            copy_allowed: v.copy_allowed.clone(),
294                            paste_allowed: v.paste_allowed.clone(),
295                            download_allowed: v.download_allowed.clone(),
296                            upload_allowed: v.upload_allowed.clone(),
297                            print_allowed: v.print_allowed.clone(),
298                            disconnect_timeout_in_minutes: v.disconnect_timeout_in_minutes,
299                            idle_disconnect_timeout_in_minutes: v
300                                .idle_disconnect_timeout_in_minutes,
301                            associated_portal_arns: v.associated_portal_arns.clone(),
302                            tags: v.tags.clone(),
303                        },
304                    )
305                })
306                .collect(),
307            identity_providers: state
308                .identity_providers
309                .iter()
310                .map(|(k, v)| {
311                    (
312                        k.clone(),
313                        IdentityProviderView {
314                            identity_provider_arn: v.identity_provider_arn.clone(),
315                            portal_arn: v.portal_arn.clone(),
316                            identity_provider_name: v.identity_provider_name.clone(),
317                            identity_provider_type: v.identity_provider_type.clone(),
318                            identity_provider_details: v.identity_provider_details.clone(),
319                        },
320                    )
321                })
322                .collect(),
323            ip_access_settings: state
324                .ip_access_settings
325                .iter()
326                .map(|(k, v)| {
327                    (
328                        k.clone(),
329                        IpAccessSettingsView {
330                            ip_access_settings_arn: v.ip_access_settings_arn.clone(),
331                            display_name: v.display_name.clone(),
332                            description: v.description.clone(),
333                            ip_rules: v
334                                .ip_rules
335                                .iter()
336                                .map(|r| IpRuleView {
337                                    ip_range: r.ip_range.clone(),
338                                    description: r.description.clone(),
339                                })
340                                .collect(),
341                            associated_portal_arns: v.associated_portal_arns.clone(),
342                            creation_date: v.creation_date.to_rfc3339(),
343                            tags: v.tags.clone(),
344                        },
345                    )
346                })
347                .collect(),
348            trust_stores: state
349                .trust_stores
350                .iter()
351                .map(|(k, v)| {
352                    (
353                        k.clone(),
354                        TrustStoreView {
355                            trust_store_arn: v.trust_store_arn.clone(),
356                            certificate_list: v
357                                .certificate_list
358                                .iter()
359                                .map(|c| TrustStoreCertificateView {
360                                    thumbprint: c.thumbprint.clone(),
361                                    body: c.body.clone(),
362                                    issuer: c.issuer.clone(),
363                                    subject: c.subject.clone(),
364                                })
365                                .collect(),
366                            associated_portal_arns: v.associated_portal_arns.clone(),
367                            tags: v.tags.clone(),
368                        },
369                    )
370                })
371                .collect(),
372            data_protection_settings: state
373                .data_protection_settings
374                .iter()
375                .map(|(k, v)| {
376                    (
377                        k.clone(),
378                        DataProtectionSettingsView {
379                            data_protection_settings_arn: v.data_protection_settings_arn.clone(),
380                            display_name: v.display_name.clone(),
381                            description: v.description.clone(),
382                            associated_portal_arns: v.associated_portal_arns.clone(),
383                            creation_date: v.creation_date.to_rfc3339(),
384                            tags: v.tags.clone(),
385                        },
386                    )
387                })
388                .collect(),
389            session_loggers: state
390                .session_loggers
391                .iter()
392                .map(|(k, v)| {
393                    (
394                        k.clone(),
395                        SessionLoggerView {
396                            session_logger_arn: v.session_logger_arn.clone(),
397                            display_name: v.display_name.clone(),
398                            associated_portal_arns: v.associated_portal_arns.clone(),
399                            creation_date: v.creation_date.to_rfc3339(),
400                            tags: v.tags.clone(),
401                        },
402                    )
403                })
404                .collect(),
405            sessions: state
406                .sessions
407                .iter()
408                .map(|(k, v)| {
409                    (
410                        k.clone(),
411                        SessionView {
412                            session_id: v.session_id.clone(),
413                            portal_id: v.portal_id.clone(),
414                            username: v.username.clone(),
415                            status: v.status.clone(),
416                            start_time: v.start_time.to_rfc3339(),
417                            end_time: v.end_time.map(|t| t.to_rfc3339()),
418                            client_ip_addresses: v.client_ip_addresses.clone(),
419                        },
420                    )
421                })
422                .collect(),
423        }
424    }
425}
426
427// --- From view types to internal types ---
428
429impl From<WorkspacesWebStateView> for WorkspacesWebState {
430    fn from(view: WorkspacesWebStateView) -> Self {
431        use chrono::{DateTime, Utc};
432        WorkspacesWebState {
433            portals: view
434                .portals
435                .into_iter()
436                .map(|(k, v)| {
437                    let creation_date = DateTime::parse_from_rfc3339(&v.creation_date)
438                        .map(|dt| dt.with_timezone(&Utc))
439                        .unwrap_or_else(|_| Utc::now());
440                    (
441                        k,
442                        Portal {
443                            portal_arn: v.portal_arn,
444                            portal_endpoint: v.portal_endpoint,
445                            display_name: v.display_name,
446                            portal_status: v.portal_status,
447                            browser_type: v.browser_type,
448                            renderer_type: v.renderer_type,
449                            creation_date,
450                            browser_settings_arn: v.browser_settings_arn,
451                            network_settings_arn: v.network_settings_arn,
452                            user_access_logging_settings_arn: v.user_access_logging_settings_arn,
453                            user_settings_arn: v.user_settings_arn,
454                            trust_store_arn: v.trust_store_arn,
455                            ip_access_settings_arn: v.ip_access_settings_arn,
456                            data_protection_settings_arn: v.data_protection_settings_arn,
457                            session_logger_arn: v.session_logger_arn,
458                            tags: v.tags,
459                        },
460                    )
461                })
462                .collect(),
463            browser_settings: view
464                .browser_settings
465                .into_iter()
466                .map(|(k, v)| {
467                    (
468                        k,
469                        BrowserSettings {
470                            browser_settings_arn: v.browser_settings_arn,
471                            browser_policy: v.browser_policy,
472                            associated_portal_arns: v.associated_portal_arns,
473                            tags: v.tags,
474                        },
475                    )
476                })
477                .collect(),
478            network_settings: view
479                .network_settings
480                .into_iter()
481                .map(|(k, v)| {
482                    (
483                        k,
484                        NetworkSettings {
485                            network_settings_arn: v.network_settings_arn,
486                            vpc_id: v.vpc_id,
487                            subnet_ids: v.subnet_ids,
488                            security_group_ids: v.security_group_ids,
489                            associated_portal_arns: v.associated_portal_arns,
490                            tags: v.tags,
491                        },
492                    )
493                })
494                .collect(),
495            user_access_logging_settings: view
496                .user_access_logging_settings
497                .into_iter()
498                .map(|(k, v)| {
499                    (
500                        k,
501                        UserAccessLoggingSettings {
502                            user_access_logging_settings_arn: v.user_access_logging_settings_arn,
503                            kinesis_stream_arn: v.kinesis_stream_arn,
504                            associated_portal_arns: v.associated_portal_arns,
505                            tags: v.tags,
506                        },
507                    )
508                })
509                .collect(),
510            user_settings: view
511                .user_settings
512                .into_iter()
513                .map(|(k, v)| {
514                    (
515                        k,
516                        UserSettings {
517                            user_settings_arn: v.user_settings_arn,
518                            copy_allowed: v.copy_allowed,
519                            paste_allowed: v.paste_allowed,
520                            download_allowed: v.download_allowed,
521                            upload_allowed: v.upload_allowed,
522                            print_allowed: v.print_allowed,
523                            disconnect_timeout_in_minutes: v.disconnect_timeout_in_minutes,
524                            idle_disconnect_timeout_in_minutes: v
525                                .idle_disconnect_timeout_in_minutes,
526                            associated_portal_arns: v.associated_portal_arns,
527                            tags: v.tags,
528                        },
529                    )
530                })
531                .collect(),
532            identity_providers: view
533                .identity_providers
534                .into_iter()
535                .map(|(k, v)| {
536                    (
537                        k,
538                        IdentityProvider {
539                            identity_provider_arn: v.identity_provider_arn,
540                            portal_arn: v.portal_arn,
541                            identity_provider_name: v.identity_provider_name,
542                            identity_provider_type: v.identity_provider_type,
543                            identity_provider_details: v.identity_provider_details,
544                        },
545                    )
546                })
547                .collect(),
548            ip_access_settings: view
549                .ip_access_settings
550                .into_iter()
551                .map(|(k, v)| {
552                    let creation_date = DateTime::parse_from_rfc3339(&v.creation_date)
553                        .map(|dt| dt.with_timezone(&Utc))
554                        .unwrap_or_else(|_| Utc::now());
555                    (
556                        k,
557                        IpAccessSettings {
558                            ip_access_settings_arn: v.ip_access_settings_arn,
559                            display_name: v.display_name,
560                            description: v.description,
561                            ip_rules: v
562                                .ip_rules
563                                .into_iter()
564                                .map(|r| IpRule {
565                                    ip_range: r.ip_range,
566                                    description: r.description,
567                                })
568                                .collect(),
569                            associated_portal_arns: v.associated_portal_arns,
570                            creation_date,
571                            tags: v.tags,
572                        },
573                    )
574                })
575                .collect(),
576            trust_stores: view
577                .trust_stores
578                .into_iter()
579                .map(|(k, v)| {
580                    (
581                        k,
582                        TrustStore {
583                            trust_store_arn: v.trust_store_arn,
584                            certificate_list: v
585                                .certificate_list
586                                .into_iter()
587                                .map(|c| TrustStoreCertificate {
588                                    thumbprint: c.thumbprint,
589                                    body: c.body,
590                                    issuer: c.issuer,
591                                    subject: c.subject,
592                                })
593                                .collect(),
594                            associated_portal_arns: v.associated_portal_arns,
595                            tags: v.tags,
596                        },
597                    )
598                })
599                .collect(),
600            data_protection_settings: view
601                .data_protection_settings
602                .into_iter()
603                .map(|(k, v)| {
604                    let creation_date = DateTime::parse_from_rfc3339(&v.creation_date)
605                        .map(|dt| dt.with_timezone(&Utc))
606                        .unwrap_or_else(|_| Utc::now());
607                    (
608                        k,
609                        DataProtectionSettings {
610                            data_protection_settings_arn: v.data_protection_settings_arn,
611                            display_name: v.display_name,
612                            description: v.description,
613                            associated_portal_arns: v.associated_portal_arns,
614                            creation_date,
615                            tags: v.tags,
616                        },
617                    )
618                })
619                .collect(),
620            session_loggers: view
621                .session_loggers
622                .into_iter()
623                .map(|(k, v)| {
624                    let creation_date = DateTime::parse_from_rfc3339(&v.creation_date)
625                        .map(|dt| dt.with_timezone(&Utc))
626                        .unwrap_or_else(|_| Utc::now());
627                    (
628                        k,
629                        SessionLogger {
630                            session_logger_arn: v.session_logger_arn,
631                            display_name: v.display_name,
632                            associated_portal_arns: v.associated_portal_arns,
633                            creation_date,
634                            tags: v.tags,
635                        },
636                    )
637                })
638                .collect(),
639            sessions: view
640                .sessions
641                .into_iter()
642                .map(|(k, v)| {
643                    let start_time = DateTime::parse_from_rfc3339(&v.start_time)
644                        .map(|dt| dt.with_timezone(&Utc))
645                        .unwrap_or_else(|_| Utc::now());
646                    let end_time = v.end_time.as_deref().and_then(|t| {
647                        DateTime::parse_from_rfc3339(t)
648                            .ok()
649                            .map(|dt| dt.with_timezone(&Utc))
650                    });
651                    (
652                        k,
653                        Session {
654                            session_id: v.session_id,
655                            portal_id: v.portal_id,
656                            username: v.username,
657                            status: v.status,
658                            start_time,
659                            end_time,
660                            client_ip_addresses: v.client_ip_addresses,
661                        },
662                    )
663                })
664                .collect(),
665        }
666    }
667}
668
669// --- StatefulService implementation ---
670
671impl StatefulService for WorkspacesWebService {
672    type StateView = WorkspacesWebStateView;
673
674    async fn snapshot(&self, account_id: &str, region: &str) -> Self::StateView {
675        let state = self.state.get(account_id, region);
676        let guard = state.read().await;
677        WorkspacesWebStateView::from(&*guard)
678    }
679
680    async fn restore(
681        &self,
682        account_id: &str,
683        region: &str,
684        view: Self::StateView,
685    ) -> Result<(), StateViewError> {
686        let state = self.state.get(account_id, region);
687        {
688            let mut guard = state.write().await;
689            *guard = WorkspacesWebState::from(view);
690        }
691        self.notify_state_changed(account_id, region).await;
692        Ok(())
693    }
694
695    async fn merge(
696        &self,
697        account_id: &str,
698        region: &str,
699        view: Self::StateView,
700    ) -> Result<(), StateViewError> {
701        let state = self.state.get(account_id, region);
702        {
703            let mut guard = state.write().await;
704            let new_state = WorkspacesWebState::from(view);
705            guard.portals.extend(new_state.portals);
706            guard.browser_settings.extend(new_state.browser_settings);
707            guard.network_settings.extend(new_state.network_settings);
708            guard
709                .user_access_logging_settings
710                .extend(new_state.user_access_logging_settings);
711            guard.user_settings.extend(new_state.user_settings);
712            guard
713                .identity_providers
714                .extend(new_state.identity_providers);
715            guard
716                .ip_access_settings
717                .extend(new_state.ip_access_settings);
718            guard.trust_stores.extend(new_state.trust_stores);
719            guard
720                .data_protection_settings
721                .extend(new_state.data_protection_settings);
722            guard.session_loggers.extend(new_state.session_loggers);
723            guard.sessions.extend(new_state.sessions);
724        }
725        self.notify_state_changed(account_id, region).await;
726        Ok(())
727    }
728
729    fn notifier(&self) -> &StateChangeNotifier<Self::StateView> {
730        &self.notifier
731    }
732}