1use bitwarden_api_api::models::{
2 DomainsResponseModel, ProfileOrganizationResponseModel, ProfileResponseModel, SyncResponseModel,
3};
4use bitwarden_core::{
5 client::encryption_settings::EncryptionSettingsError, require, Client, MissingFieldError,
6};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9use uuid::Uuid;
10
11use crate::{Cipher, Collection, Folder, GlobalDomains, VaultParseError};
12
13#[derive(Debug, Error)]
14pub enum SyncError {
15 #[error(transparent)]
16 Api(#[from] bitwarden_core::ApiError),
17 #[error(transparent)]
18 MissingField(#[from] MissingFieldError),
19 #[error(transparent)]
20 VaultParse(#[from] VaultParseError),
21 #[error(transparent)]
22 EncryptionSettings(#[from] EncryptionSettingsError),
23}
24
25#[derive(Serialize, Deserialize, Debug)]
26#[serde(rename_all = "camelCase", deny_unknown_fields)]
27pub struct SyncRequest {
28 pub exclude_subdomains: Option<bool>,
30}
31
32pub(crate) async fn sync(client: &Client, input: &SyncRequest) -> Result<SyncResponse, SyncError> {
33 let config = client.internal.get_api_configurations().await;
34 let sync = bitwarden_api_api::apis::sync_api::sync_get(&config.api, input.exclude_subdomains)
35 .await
36 .map_err(|e| SyncError::Api(e.into()))?;
37
38 let org_keys: Vec<_> = require!(sync.profile.as_ref())
39 .organizations
40 .as_deref()
41 .unwrap_or_default()
42 .iter()
43 .filter_map(|o| o.id.zip(o.key.as_deref().and_then(|k| k.parse().ok())))
44 .collect();
45
46 client.internal.initialize_org_crypto(org_keys)?;
47
48 SyncResponse::process_response(sync)
49}
50
51#[derive(Serialize, Deserialize, Debug)]
52#[serde(rename_all = "camelCase", deny_unknown_fields)]
53pub struct ProfileResponse {
54 pub id: Uuid,
55 pub name: String,
56 pub email: String,
57
58 pub organizations: Vec<ProfileOrganizationResponse>,
61}
62
63#[derive(Serialize, Deserialize, Debug)]
64#[serde(rename_all = "camelCase", deny_unknown_fields)]
65pub struct ProfileOrganizationResponse {
66 pub id: Uuid,
67}
68
69#[derive(Serialize, Deserialize, Debug)]
70#[serde(rename_all = "camelCase", deny_unknown_fields)]
71pub struct DomainResponse {
72 pub equivalent_domains: Vec<Vec<String>>,
73 pub global_equivalent_domains: Vec<GlobalDomains>,
74}
75
76#[derive(Serialize, Deserialize, Debug)]
77#[serde(rename_all = "camelCase", deny_unknown_fields)]
78pub struct SyncResponse {
79 pub profile: ProfileResponse,
82 pub folders: Vec<Folder>,
83 pub collections: Vec<Collection>,
84 pub ciphers: Vec<Cipher>,
86 pub domains: Option<DomainResponse>,
87 }
90
91impl SyncResponse {
92 pub(crate) fn process_response(response: SyncResponseModel) -> Result<SyncResponse, SyncError> {
93 let profile = require!(response.profile);
94 let ciphers = require!(response.ciphers);
95
96 fn try_into_iter<In, InItem, Out, OutItem>(iter: In) -> Result<Out, InItem::Error>
97 where
98 In: IntoIterator<Item = InItem>,
99 InItem: TryInto<OutItem>,
100 Out: FromIterator<OutItem>,
101 {
102 iter.into_iter().map(|i| i.try_into()).collect()
103 }
104
105 Ok(SyncResponse {
106 profile: ProfileResponse::process_response(*profile)?,
107 folders: try_into_iter(require!(response.folders))?,
108 collections: try_into_iter(require!(response.collections))?,
109 ciphers: try_into_iter(ciphers)?,
110 domains: response.domains.map(|d| (*d).try_into()).transpose()?,
111 })
114 }
115}
116
117impl ProfileOrganizationResponse {
118 fn process_response(
119 response: ProfileOrganizationResponseModel,
120 ) -> Result<ProfileOrganizationResponse, MissingFieldError> {
121 Ok(ProfileOrganizationResponse {
122 id: require!(response.id),
123 })
124 }
125}
126
127impl ProfileResponse {
128 fn process_response(
129 response: ProfileResponseModel,
130 ) -> Result<ProfileResponse, MissingFieldError> {
131 Ok(ProfileResponse {
132 id: require!(response.id),
133 name: require!(response.name),
134 email: require!(response.email),
135 organizations: response
138 .organizations
139 .unwrap_or_default()
140 .into_iter()
141 .map(ProfileOrganizationResponse::process_response)
142 .collect::<Result<_, _>>()?,
143 })
144 }
145}
146
147impl TryFrom<DomainsResponseModel> for DomainResponse {
148 type Error = SyncError;
149
150 fn try_from(value: DomainsResponseModel) -> Result<Self, Self::Error> {
151 Ok(Self {
152 equivalent_domains: value.equivalent_domains.unwrap_or_default(),
153 global_equivalent_domains: value
154 .global_equivalent_domains
155 .unwrap_or_default()
156 .into_iter()
157 .map(|s| s.try_into())
158 .collect::<Result<Vec<GlobalDomains>, _>>()?,
159 })
160 }
161}