bitwarden_vault/folder/
edit.rs

1use 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    // Verify the folder we're updating exists
40    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}