bitwarden_policies/
policy_sync_handler.rs1use std::sync::Arc;
2
3use bitwarden_core::{FromClient, require};
4use bitwarden_state::repository::{Repository, RepositoryOption};
5use bitwarden_sync::{SyncHandler, SyncHandlerError};
6
7use crate::{Policy, PolicyId};
8
9#[derive(FromClient)]
13pub struct PolicySyncHandler {
14 repository: Option<Arc<dyn Repository<Policy>>>,
15}
16
17#[async_trait::async_trait]
18impl SyncHandler for PolicySyncHandler {
19 async fn on_sync(
20 &self,
21 response: &bitwarden_api_api::models::SyncResponseModel,
22 ) -> Result<(), SyncHandlerError> {
23 let repository = self.repository.require()?;
24 let api_policies = require!(
27 response
28 .policies_new
29 .as_ref()
30 .or(response.policies.as_ref())
31 );
32
33 let policies: Vec<(PolicyId, Policy)> = api_policies
34 .iter()
35 .filter_map(|p| {
36 Policy::try_from(p.clone())
37 .inspect_err(
38 |e| tracing::error!(id = ?p.id, error = ?e, "Failed to deserialize policy"),
39 )
40 .ok()
41 .map(|policy| (policy.id, policy))
42 })
43 .collect();
44
45 repository.replace_all(policies).await?;
46
47 Ok(())
48 }
49}
50
51#[cfg(test)]
52mod tests {
53 use std::sync::Arc;
54
55 use bitwarden_api_api::models::{PolicyResponseModel, PolicyType, SyncResponseModel};
56 use bitwarden_test::MemoryRepository;
57
58 use super::*;
59
60 fn make_policy_response(id: uuid::Uuid, r#type: PolicyType) -> PolicyResponseModel {
61 PolicyResponseModel {
62 object: Some("policy".to_string()),
63 id: Some(id),
64 organization_id: Some(uuid::Uuid::new_v4()),
65 r#type: Some(r#type),
66 data: None,
67 enabled: Some(true),
68 revision_date: Some("2025-01-01T00:00:00Z".to_string()),
69 }
70 }
71
72 #[tokio::test]
73 async fn test_on_sync_replaces_existing_policies() {
74 let repository = Arc::new(MemoryRepository::<Policy>::default());
75 let handler = PolicySyncHandler {
76 repository: Some(repository.clone()),
77 };
78
79 let id1 = uuid::Uuid::new_v4();
81 let id2 = uuid::Uuid::new_v4();
82 let response = SyncResponseModel {
83 policies_new: Some(vec![
84 make_policy_response(id1, PolicyType::MasterPassword),
85 make_policy_response(id2, PolicyType::SingleOrg),
86 ]),
87 ..Default::default()
88 };
89 handler.on_sync(&response).await.unwrap();
90 assert_eq!(repository.list().await.unwrap().len(), 2);
91
92 let id3 = uuid::Uuid::new_v4();
94 let response = SyncResponseModel {
95 policies_new: Some(vec![make_policy_response(id3, PolicyType::RequireSso)]),
96 ..Default::default()
97 };
98 handler.on_sync(&response).await.unwrap();
99
100 let stored = repository.list().await.unwrap();
101 assert_eq!(stored.len(), 1);
102 assert!(repository.get(PolicyId::new(id1)).await.unwrap().is_none());
103 assert!(repository.get(PolicyId::new(id2)).await.unwrap().is_none());
104 assert!(repository.get(PolicyId::new(id3)).await.unwrap().is_some());
105 }
106
107 #[tokio::test]
108 async fn test_on_sync_falls_back_to_legacy_policies() {
109 let repository = Arc::new(MemoryRepository::<Policy>::default());
110 let handler = PolicySyncHandler {
111 repository: Some(repository.clone()),
112 };
113
114 let id = uuid::Uuid::new_v4();
115 let response = SyncResponseModel {
116 policies: Some(vec![make_policy_response(id, PolicyType::MasterPassword)]),
117 ..Default::default()
118 };
119 handler.on_sync(&response).await.unwrap();
120
121 assert!(repository.get(PolicyId::new(id)).await.unwrap().is_some());
122 }
123
124 #[tokio::test]
125 async fn test_on_sync_skips_unknown_policy_type() {
126 let repository = Arc::new(MemoryRepository::<Policy>::default());
127 let handler = PolicySyncHandler {
128 repository: Some(repository.clone()),
129 };
130
131 let known_id = uuid::Uuid::new_v4();
132 let unknown_id = uuid::Uuid::new_v4();
133 let response = SyncResponseModel {
134 policies_new: Some(vec![
135 make_policy_response(known_id, PolicyType::MasterPassword),
136 make_policy_response(unknown_id, PolicyType::__Unknown(9999)),
137 ]),
138 ..Default::default()
139 };
140 handler.on_sync(&response).await.unwrap();
141
142 let stored = repository.list().await.unwrap();
144 assert_eq!(stored.len(), 1);
145 assert!(
146 repository
147 .get(PolicyId::new(known_id))
148 .await
149 .unwrap()
150 .is_some()
151 );
152 assert!(
153 repository
154 .get(PolicyId::new(unknown_id))
155 .await
156 .unwrap()
157 .is_none()
158 );
159 }
160
161 #[tokio::test]
162 async fn test_on_sync_no_policies_returns_error() {
163 let repository = Arc::new(MemoryRepository::<Policy>::default());
164 let handler = PolicySyncHandler {
165 repository: Some(repository.clone()),
166 };
167
168 let response = SyncResponseModel::default();
169 let result = handler.on_sync(&response).await;
170 assert!(result.is_err());
171 }
172}