bitwarden_vault/folder/
edit.rs1use bitwarden_api_api::apis::folders_api;
2use bitwarden_core::{key_management::KeyIds, ApiError, MissingFieldError};
3use bitwarden_crypto::{CryptoError, KeyStore};
4use bitwarden_error::bitwarden_error;
5use bitwarden_state::repository::{Repository, RepositoryError};
6use thiserror::Error;
7#[cfg(feature = "wasm")]
8use wasm_bindgen::prelude::*;
9
10use crate::{Folder, FolderAddEditRequest, FolderView, ItemNotFoundError, VaultParseError};
11
12#[allow(missing_docs)]
13#[bitwarden_error(flat)]
14#[derive(Debug, Error)]
15pub enum EditFolderError {
16 #[error(transparent)]
17 ItemNotFound(#[from] ItemNotFoundError),
18 #[error(transparent)]
19 Crypto(#[from] CryptoError),
20 #[error(transparent)]
21 Api(#[from] ApiError),
22 #[error(transparent)]
23 VaultParse(#[from] VaultParseError),
24 #[error(transparent)]
25 MissingField(#[from] MissingFieldError),
26 #[error(transparent)]
27 RepositoryError(#[from] RepositoryError),
28 #[error(transparent)]
29 Uuid(#[from] uuid::Error),
30}
31
32pub(super) async fn edit_folder<R: Repository<Folder> + ?Sized>(
33 key_store: &KeyStore<KeyIds>,
34 api_config: &bitwarden_api_api::apis::configuration::Configuration,
35 repository: &R,
36 folder_id: &str,
37 request: FolderAddEditRequest,
38) -> Result<FolderView, EditFolderError> {
39 repository
41 .get(folder_id.to_owned())
42 .await?
43 .ok_or(ItemNotFoundError)?;
44
45 let folder_request = key_store.encrypt(request)?;
46
47 let resp = folders_api::folders_id_put(api_config, folder_id, Some(folder_request))
48 .await
49 .map_err(ApiError::from)?;
50
51 let folder: Folder = resp.try_into()?;
52
53 debug_assert!(folder.id.unwrap_or_default().to_string() == folder_id);
54
55 repository
56 .set(folder_id.to_string(), folder.clone())
57 .await?;
58
59 Ok(key_store.decrypt(&folder)?)
60}
61
62#[cfg(test)]
63mod tests {
64 use bitwarden_api_api::{
65 apis::configuration::Configuration,
66 models::{FolderRequestModel, FolderResponseModel},
67 };
68 use bitwarden_core::key_management::SymmetricKeyId;
69 use bitwarden_crypto::{PrimitiveEncryptable, SymmetricCryptoKey};
70 use bitwarden_test::{start_api_mock, MemoryRepository};
71 use uuid::uuid;
72 use wiremock::{matchers, Mock, Request, ResponseTemplate};
73
74 use super::*;
75
76 async fn repository_add_folder(
77 repository: &MemoryRepository<Folder>,
78 store: &KeyStore<KeyIds>,
79 folder_id: uuid::Uuid,
80 name: &str,
81 ) {
82 repository
83 .set(
84 folder_id.to_string(),
85 Folder {
86 id: Some(folder_id),
87 name: name
88 .encrypt(&mut store.context(), SymmetricKeyId::User)
89 .unwrap(),
90 revision_date: "2024-01-01T00:00:00Z".parse().unwrap(),
91 },
92 )
93 .await
94 .unwrap();
95 }
96
97 #[tokio::test]
98 async fn test_edit_folder() {
99 let store: KeyStore<KeyIds> = KeyStore::default();
100 #[allow(deprecated)]
101 let _ = store.context_mut().set_symmetric_key(
102 SymmetricKeyId::User,
103 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
104 );
105
106 let folder_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
107
108 let (_server, api_config) = start_api_mock(vec![Mock::given(matchers::path(format!(
109 "/folders/{}",
110 folder_id
111 )))
112 .respond_with(move |req: &Request| {
113 let body: FolderRequestModel = req.body_json().unwrap();
114 ResponseTemplate::new(200).set_body_json(FolderResponseModel {
115 object: Some("folder".to_string()),
116 id: Some(folder_id),
117 name: Some(body.name),
118 revision_date: Some("2025-01-01T00:00:00Z".to_string()),
119 })
120 })
121 .expect(1)])
122 .await;
123
124 let repository = MemoryRepository::<Folder>::default();
125 repository_add_folder(&repository, &store, folder_id, "old_name").await;
126
127 let result = edit_folder(
128 &store,
129 &api_config,
130 &repository,
131 &folder_id.to_string(),
132 FolderAddEditRequest {
133 name: "test".to_string(),
134 },
135 )
136 .await
137 .unwrap();
138
139 assert_eq!(
140 result,
141 FolderView {
142 id: Some(folder_id),
143 name: "test".to_string(),
144 revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
145 }
146 );
147 }
148
149 #[tokio::test]
150 async fn test_edit_folder_does_not_exist() {
151 let store: KeyStore<KeyIds> = KeyStore::default();
152
153 let repository = MemoryRepository::<Folder>::default();
154 let folder_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
155
156 let result = edit_folder(
157 &store,
158 &Configuration::default(),
159 &repository,
160 &folder_id.to_string(),
161 FolderAddEditRequest {
162 name: "test".to_string(),
163 },
164 )
165 .await;
166
167 assert!(result.is_err());
168 assert!(matches!(
169 result.unwrap_err(),
170 EditFolderError::ItemNotFound(_)
171 ));
172 }
173
174 #[tokio::test]
175 async fn test_edit_folder_http_error() {
176 let store: KeyStore<KeyIds> = KeyStore::default();
177 #[allow(deprecated)]
178 let _ = store.context_mut().set_symmetric_key(
179 SymmetricKeyId::User,
180 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
181 );
182
183 let folder_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
184
185 let (_server, api_config) = start_api_mock(vec![Mock::given(matchers::path(format!(
186 "/folders/{}",
187 folder_id
188 )))
189 .respond_with(ResponseTemplate::new(500))])
190 .await;
191
192 let repository = MemoryRepository::<Folder>::default();
193 repository_add_folder(&repository, &store, folder_id, "old_name").await;
194
195 let result = edit_folder(
196 &store,
197 &api_config,
198 &repository,
199 &folder_id.to_string(),
200 FolderAddEditRequest {
201 name: "test".to_string(),
202 },
203 )
204 .await;
205
206 assert!(result.is_err());
207 assert!(matches!(result.unwrap_err(), EditFolderError::Api(_)));
208 }
209}