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 use crate::FolderId;
76
77 async fn repository_add_folder(
78 repository: &MemoryRepository<Folder>,
79 store: &KeyStore<KeyIds>,
80 folder_id: FolderId,
81 name: &str,
82 ) {
83 repository
84 .set(
85 folder_id.to_string(),
86 Folder {
87 id: Some(folder_id),
88 name: name
89 .encrypt(&mut store.context(), SymmetricKeyId::User)
90 .unwrap(),
91 revision_date: "2024-01-01T00:00:00Z".parse().unwrap(),
92 },
93 )
94 .await
95 .unwrap();
96 }
97
98 #[tokio::test]
99 async fn test_edit_folder() {
100 let store: KeyStore<KeyIds> = KeyStore::default();
101 #[allow(deprecated)]
102 let _ = store.context_mut().set_symmetric_key(
103 SymmetricKeyId::User,
104 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
105 );
106
107 let folder_id: FolderId = "25afb11c-9c95-4db5-8bac-c21cb204a3f1".parse().unwrap();
108
109 let (_server, api_config) = start_api_mock(vec![Mock::given(matchers::path(format!(
110 "/folders/{}",
111 folder_id
112 )))
113 .respond_with(move |req: &Request| {
114 let body: FolderRequestModel = req.body_json().unwrap();
115 ResponseTemplate::new(200).set_body_json(FolderResponseModel {
116 object: Some("folder".to_string()),
117 id: Some(folder_id.into()),
118 name: Some(body.name),
119 revision_date: Some("2025-01-01T00:00:00Z".to_string()),
120 })
121 })
122 .expect(1)])
123 .await;
124
125 let repository = MemoryRepository::<Folder>::default();
126 repository_add_folder(&repository, &store, folder_id, "old_name").await;
127
128 let result = edit_folder(
129 &store,
130 &api_config,
131 &repository,
132 &folder_id.to_string(),
133 FolderAddEditRequest {
134 name: "test".to_string(),
135 },
136 )
137 .await
138 .unwrap();
139
140 assert_eq!(
141 result,
142 FolderView {
143 id: Some(folder_id),
144 name: "test".to_string(),
145 revision_date: "2025-01-01T00:00:00Z".parse().unwrap(),
146 }
147 );
148 }
149
150 #[tokio::test]
151 async fn test_edit_folder_does_not_exist() {
152 let store: KeyStore<KeyIds> = KeyStore::default();
153
154 let repository = MemoryRepository::<Folder>::default();
155 let folder_id = uuid!("25afb11c-9c95-4db5-8bac-c21cb204a3f1");
156
157 let result = edit_folder(
158 &store,
159 &Configuration::default(),
160 &repository,
161 &folder_id.to_string(),
162 FolderAddEditRequest {
163 name: "test".to_string(),
164 },
165 )
166 .await;
167
168 assert!(result.is_err());
169 assert!(matches!(
170 result.unwrap_err(),
171 EditFolderError::ItemNotFound(_)
172 ));
173 }
174
175 #[tokio::test]
176 async fn test_edit_folder_http_error() {
177 let store: KeyStore<KeyIds> = KeyStore::default();
178 #[allow(deprecated)]
179 let _ = store.context_mut().set_symmetric_key(
180 SymmetricKeyId::User,
181 SymmetricCryptoKey::make_aes256_cbc_hmac_key(),
182 );
183
184 let folder_id: FolderId = "25afb11c-9c95-4db5-8bac-c21cb204a3f1".parse().unwrap();
185
186 let (_server, api_config) = start_api_mock(vec![Mock::given(matchers::path(format!(
187 "/folders/{}",
188 folder_id
189 )))
190 .respond_with(ResponseTemplate::new(500))])
191 .await;
192
193 let repository = MemoryRepository::<Folder>::default();
194 repository_add_folder(&repository, &store, folder_id, "old_name").await;
195
196 let result = edit_folder(
197 &store,
198 &api_config,
199 &repository,
200 &folder_id.to_string(),
201 FolderAddEditRequest {
202 name: "test".to_string(),
203 },
204 )
205 .await;
206
207 assert!(result.is_err());
208 assert!(matches!(result.unwrap_err(), EditFolderError::Api(_)));
209 }
210}