bitwarden_server_communication_config/
config.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5#[cfg_attr(
6 feature = "wasm",
7 derive(tsify::Tsify),
8 tsify(into_wasm_abi, from_wasm_abi)
9)]
10#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
11pub struct ServerCommunicationConfig {
12 pub bootstrap: BootstrapConfig,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18#[cfg_attr(
19 feature = "wasm",
20 derive(tsify::Tsify),
21 tsify(into_wasm_abi, from_wasm_abi)
22)]
23#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
24#[serde(tag = "type", rename_all = "camelCase")]
25pub enum BootstrapConfig {
26 Direct,
28 SsoCookieVendor(SsoCookieVendorConfig),
30}
31
32#[derive(Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37#[cfg_attr(
38 feature = "wasm",
39 derive(tsify::Tsify),
40 tsify(into_wasm_abi, from_wasm_abi)
41)]
42#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
43pub struct SsoCookieVendorConfig {
44 pub idp_login_url: Option<String>,
46 pub cookie_name: Option<String>,
48 pub cookie_domain: Option<String>,
50 pub cookie_value: Option<Vec<crate::AcquiredCookie>>,
56}
57
58impl std::fmt::Debug for SsoCookieVendorConfig {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("SsoCookieVendorConfig")
62 .field("idp_login_url", &self.idp_login_url)
63 .field("cookie_name", &self.cookie_name)
64 .field("cookie_domain", &self.cookie_domain)
65 .field(
66 "cookie_value",
67 &self.cookie_value.as_ref().map(|_| "[REDACTED]"),
68 )
69 .finish()
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn direct_bootstrap_serialization() {
79 let config = ServerCommunicationConfig {
80 bootstrap: BootstrapConfig::Direct,
81 };
82
83 let json = serde_json::to_string(&config).unwrap();
84 assert!(json.contains("\"type\":\"direct\""));
85
86 let deserialized: ServerCommunicationConfig = serde_json::from_str(&json).unwrap();
87 assert!(matches!(deserialized.bootstrap, BootstrapConfig::Direct));
88 }
89
90 #[test]
91 fn sso_cookie_vendor_serialization() {
92 let config = ServerCommunicationConfig {
93 bootstrap: BootstrapConfig::SsoCookieVendor(SsoCookieVendorConfig {
94 idp_login_url: Some("https://timeloop-auth.acme.com/login".to_string()),
95 cookie_name: Some("ALBAuthSessionCookie".to_string()),
96 cookie_domain: Some("vault.example.com".to_string()),
97 cookie_value: None,
98 }),
99 };
100
101 let json = serde_json::to_string(&config).unwrap();
102 assert!(json.contains("\"type\":\"ssoCookieVendor\""));
103 assert!(json.contains("timeloop-auth.acme.com"));
104 assert!(json.contains("ALBAuthSessionCookie"));
105
106 let server_json = r#"{"bootstrap":{"type":"ssoCookieVendor","idpLoginUrl":"https://idp.example.com/login","cookieName":"TestCookie","cookieDomain":"example.com"}}"#;
108 let parsed = serde_json::from_str::<ServerCommunicationConfig>(server_json).unwrap();
109 if let BootstrapConfig::SsoCookieVendor(vendor) = parsed.bootstrap {
110 assert_eq!(
111 vendor.idp_login_url,
112 Some("https://idp.example.com/login".to_string())
113 );
114 assert_eq!(vendor.cookie_name, Some("TestCookie".to_string()));
115 assert_eq!(vendor.cookie_domain, Some("example.com".to_string()));
116 } else {
117 panic!("Expected SsoCookieVendor variant");
118 }
119
120 let deserialized: ServerCommunicationConfig = serde_json::from_str(&json).unwrap();
121 if let BootstrapConfig::SsoCookieVendor(vendor_config) = deserialized.bootstrap {
122 assert_eq!(
123 vendor_config.idp_login_url,
124 Some("https://timeloop-auth.acme.com/login".to_string())
125 );
126 assert_eq!(
127 vendor_config.cookie_name,
128 Some("ALBAuthSessionCookie".to_string())
129 );
130 assert_eq!(
131 vendor_config.cookie_domain,
132 Some("vault.example.com".to_string())
133 );
134 assert!(vendor_config.cookie_value.is_none());
135 } else {
136 panic!("Expected SsoCookieVendor variant");
137 }
138 }
139
140 #[test]
141 fn cookie_value_some_and_none() {
142 use crate::AcquiredCookie;
143
144 let config_none = SsoCookieVendorConfig {
146 idp_login_url: Some("https://example.com".to_string()),
147 cookie_name: Some("TestCookie".to_string()),
148 cookie_domain: Some("example.com".to_string()),
149 cookie_value: None,
150 };
151
152 let json_none = serde_json::to_string(&config_none).unwrap();
153 let deserialized_none: SsoCookieVendorConfig = serde_json::from_str(&json_none).unwrap();
154 assert!(deserialized_none.cookie_value.is_none());
155
156 let config_some = SsoCookieVendorConfig {
158 idp_login_url: Some("https://example.com".to_string()),
159 cookie_name: Some("TestCookie".to_string()),
160 cookie_domain: Some("example.com".to_string()),
161 cookie_value: Some(vec![AcquiredCookie {
162 name: "TestCookie".to_string(),
163 value: "eyJhbGciOiJFUzI1NiIsImtpZCI6Im...".to_string(),
164 }]),
165 };
166
167 let json_some = serde_json::to_string(&config_some).unwrap();
168 let deserialized_some: SsoCookieVendorConfig = serde_json::from_str(&json_some).unwrap();
169 assert_eq!(deserialized_some.cookie_value.as_ref().unwrap().len(), 1);
170 assert_eq!(
171 deserialized_some.cookie_value.as_ref().unwrap()[0].name,
172 "TestCookie"
173 );
174
175 let config_sharded = SsoCookieVendorConfig {
177 idp_login_url: Some("https://example.com".to_string()),
178 cookie_name: Some("TestCookie".to_string()),
179 cookie_domain: Some("example.com".to_string()),
180 cookie_value: Some(vec![
181 AcquiredCookie {
182 name: "TestCookie-0".to_string(),
183 value: "shard1".to_string(),
184 },
185 AcquiredCookie {
186 name: "TestCookie-1".to_string(),
187 value: "shard2".to_string(),
188 },
189 AcquiredCookie {
190 name: "TestCookie-2".to_string(),
191 value: "shard3".to_string(),
192 },
193 ]),
194 };
195
196 let json_sharded = serde_json::to_string(&config_sharded).unwrap();
197 let deserialized_sharded: SsoCookieVendorConfig =
198 serde_json::from_str(&json_sharded).unwrap();
199 assert_eq!(deserialized_sharded.cookie_value.as_ref().unwrap().len(), 3);
200 assert_eq!(
201 deserialized_sharded.cookie_value.as_ref().unwrap()[0].name,
202 "TestCookie-0"
203 );
204 }
205
206 #[test]
207 fn enum_variants() {
208 let direct = BootstrapConfig::Direct;
209 assert!(matches!(direct, BootstrapConfig::Direct));
210
211 let vendor = BootstrapConfig::SsoCookieVendor(SsoCookieVendorConfig {
212 idp_login_url: Some("https://example.com".to_string()),
213 cookie_name: Some("Cookie".to_string()),
214 cookie_domain: Some("example.com".to_string()),
215 cookie_value: None,
216 });
217 assert!(matches!(vendor, BootstrapConfig::SsoCookieVendor(_)));
218 }
219
220 #[test]
221 fn debug_output_redacts_cookie_value() {
222 use crate::AcquiredCookie;
223
224 let config_with_cookie = SsoCookieVendorConfig {
226 idp_login_url: Some("https://example.com/login".to_string()),
227 cookie_name: Some("SessionCookie".to_string()),
228 cookie_domain: Some("example.com".to_string()),
229 cookie_value: Some(vec![AcquiredCookie {
230 name: "SessionCookie".to_string(),
231 value: "super-secret-cookie-value-abc123".to_string(),
232 }]),
233 };
234
235 let debug_output = format!("{:?}", config_with_cookie);
236
237 assert!(debug_output.contains("SsoCookieVendorConfig"));
239 assert!(debug_output.contains("example.com/login"));
240 assert!(debug_output.contains("SessionCookie"));
241 assert!(debug_output.contains("example.com"));
242
243 assert!(!debug_output.contains("super-secret-cookie-value-abc123"));
245 assert!(debug_output.contains("[REDACTED]"));
247 }
248}