Skip to main content

bitwarden_managed_settings/
managed_settings_client.rs

1use std::sync::{Arc, RwLock};
2
3use bitwarden_managed_settings_types::ManagementProfile;
4
5/// Handle to the host system's Unified Endpoint Management profile.
6///
7/// The host application constructs one of these at boot and pushes profiles into it. Clones share
8/// the underlying profile, so an update pushed through one clone is observed by all of them.
9#[derive(Clone)]
10pub struct ManagedSettingsClient {
11    profile: Arc<RwLock<Option<ManagementProfile>>>,
12}
13
14impl Default for ManagedSettingsClient {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl ManagedSettingsClient {
21    /// Fresh handle with no active profile. The host should call this once at boot.
22    pub fn new() -> Self {
23        Self {
24            profile: Arc::new(RwLock::new(None)),
25        }
26    }
27
28    /// The shared profile cell, so a constructed SDK client can read the same profile the host
29    /// pushes into this handle.
30    pub fn cell(&self) -> Arc<RwLock<Option<ManagementProfile>>> {
31        self.profile.clone()
32    }
33
34    /// The active profile, or `None` when the host has not pushed one.
35    pub fn current_profile(&self) -> Option<ManagementProfile> {
36        self.profile
37            .read()
38            .expect("managed-settings cell poisoned")
39            .clone()
40    }
41
42    /// Replace the active profile. Clear the profile with `None`.
43    pub fn update_profile(&self, profile: Option<ManagementProfile>) {
44        *self
45            .profile
46            .write()
47            .expect("managed-settings cell poisoned") = profile;
48    }
49
50    /// Returns `true` if `key` is present in the active profile.
51    pub fn is_managed(&self, key: String) -> bool {
52        self.profile
53            .read()
54            .expect("managed-settings cell poisoned")
55            .as_ref()
56            .is_some_and(|p| p.is_managed(&key))
57    }
58
59    /// Raw JSON-encoded value for `key`, if a value is present.
60    pub fn get(&self, key: String) -> Option<String> {
61        self.profile
62            .read()
63            .expect("managed-settings cell poisoned")
64            .as_ref()
65            .and_then(|p| p.get(&key))
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use std::collections::HashMap;
72
73    use super::*;
74
75    fn profile_with(key: &str, json_value: &str) -> ManagementProfile {
76        ManagementProfile {
77            version: 1,
78            updated_at: 1_750_000_000,
79            settings: HashMap::from([(key.to_string(), json_value.to_string())]),
80        }
81    }
82
83    #[test]
84    fn a_new_client_manages_nothing() {
85        let client = ManagedSettingsClient::new();
86
87        assert_eq!(client.get("environment.base".to_string()), None);
88        assert!(!client.is_managed("environment.base".to_string()));
89        assert_eq!(client.current_profile(), None);
90    }
91
92    #[test]
93    fn get_reflects_an_updated_profile() {
94        let client = ManagedSettingsClient::new();
95        let profile = profile_with("environment.base", "\"https://vault.example.com\"");
96
97        client.update_profile(Some(profile.clone()));
98
99        assert_eq!(
100            client.get("environment.base".to_string()),
101            Some("\"https://vault.example.com\"".to_string())
102        );
103        assert!(client.is_managed("environment.base".to_string()));
104        assert_eq!(client.current_profile(), Some(profile));
105    }
106
107    #[test]
108    fn updating_with_none_clears_the_profile() {
109        let client = ManagedSettingsClient::new();
110        client.update_profile(Some(profile_with("environment.base", "\"https://a\"")));
111
112        client.update_profile(None);
113
114        assert_eq!(client.get("environment.base".to_string()), None);
115        assert!(!client.is_managed("environment.base".to_string()));
116        assert_eq!(client.current_profile(), None);
117    }
118
119    #[test]
120    fn a_clone_observes_an_update_made_on_the_original() {
121        let client = ManagedSettingsClient::new();
122        let clone = client.clone();
123        let profile = profile_with("environment.base", "\"https://vault.example.com\"");
124
125        client.update_profile(Some(profile.clone()));
126
127        assert_eq!(
128            clone.get("environment.base".to_string()),
129            Some("\"https://vault.example.com\"".to_string())
130        );
131        assert_eq!(clone.current_profile(), Some(profile));
132    }
133
134    #[test]
135    fn the_shared_cell_observes_an_update_made_through_the_handle() {
136        let client = ManagedSettingsClient::new();
137        let cell = client.cell();
138        let profile = profile_with("environment.base", "\"https://vault.example.com\"");
139
140        client.update_profile(Some(profile.clone()));
141
142        assert_eq!(
143            *cell.read().expect("managed-settings cell poisoned"),
144            Some(profile)
145        );
146    }
147}