From b78835c65f7a082d4e73b19fec2ef79dd2106e91 Mon Sep 17 00:00:00 2001 From: Stephen <11358399+nixuno@users.noreply.github.com> Date: Mon, 5 May 2025 15:44:04 -0400 Subject: [PATCH 1/2] Add bulk_collections endpoint --- src/api/core/ciphers.rs | 65 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 6b883bab..0b6faae6 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -86,6 +86,7 @@ pub fn routes() -> Vec { post_collections_update, post_collections_admin, put_collections_admin, + bulk_collections ] } @@ -724,6 +725,19 @@ struct CollectionsAdminData { collection_ids: Vec, } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct BulkCollectionsData { + #[serde(alias = "OrganizationId")] + organization_id: OrganizationId, + #[serde(alias = "CollectionIds")] + collection_ids: Vec, + #[serde(alias = "CipherIds")] + cipher_ids: Vec, + #[serde(alias = "RemoveCollections")] + remove_collections: bool, +} + #[put("/ciphers//collections_v2", data = "")] async fn put_collections2_update( cipher_id: CipherId, @@ -751,6 +765,57 @@ async fn post_collections2_update( }))) } +#[post("/ciphers/bulk_collections", data = "")] +async fn post_bulk_collections( + data: Json, + headers: Headers, + mut conn: DbConn, + nt: Notify<'_>, +) -> JsonResult { + let data: BulkCollectionsData = data.into_inner(); + + let Some(org) = Organization::find_by_uuid(&data.organization_id, &mut conn).await else { + err!("Organization doesn't exist") + }; + + if !org.is_admin(&headers.user.uuid, &mut conn).await { + err!("You don't have permission to add item to organization") + } + + let mut ciphers = Vec::with_capacity(data.cipher_ids.len()); + for cipher_id in data.cipher_ids { + let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &mut conn).await else { + err!("Cipher doesn't exist") + }; + + if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { + err!("Cipher is not write accessible") + } + + ciphers.push(cipher); + } + + for collection_id in data.collection_ids { + let Some(collection) = Collection::find_by_uuid_and_org(&collection_id, &data.organization_id, &mut conn).await else { + err!("Collection doesn't exist") + }; + + if collection.is_writable_by_user(&headers.user.uuid, &mut conn).await { + for cipher in &ciphers { + if data.remove_collections { + CollectionCipher::delete(&cipher.uuid, &collection.uuid, &mut conn).await?; + } else { + CollectionCipher::save(&cipher.uuid, &collection.uuid, &mut conn).await?; + } + } + } else { + err!("No rights to modify the collection") + } + } + + Ok(()) +} + #[put("/ciphers//collections", data = "")] async fn put_collections_update( cipher_id: CipherId, From ebebf3b8655629ed1b807530b883d353f69103d8 Mon Sep 17 00:00:00 2001 From: Stephen <11358399+nixuno@users.noreply.github.com> Date: Wed, 7 May 2025 07:20:06 -0400 Subject: [PATCH 2/2] Resolve borrowing issues --- src/api/core/ciphers.rs | 67 +++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 0b6faae6..3161e8f8 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -86,7 +86,7 @@ pub fn routes() -> Vec { post_collections_update, post_collections_admin, put_collections_admin, - bulk_collections + post_bulk_collections ] } @@ -771,20 +771,11 @@ async fn post_bulk_collections( headers: Headers, mut conn: DbConn, nt: Notify<'_>, -) -> JsonResult { +) -> EmptyResult { let data: BulkCollectionsData = data.into_inner(); - let Some(org) = Organization::find_by_uuid(&data.organization_id, &mut conn).await else { - err!("Organization doesn't exist") - }; - - if !org.is_admin(&headers.user.uuid, &mut conn).await { - err!("You don't have permission to add item to organization") - } - - let mut ciphers = Vec::with_capacity(data.cipher_ids.len()); - for cipher_id in data.cipher_ids { - let Some(cipher) = Cipher::find_by_uuid(&cipher_id, &mut conn).await else { + for cipher in &data.cipher_ids { + let Some(cipher) = Cipher::find_by_uuid(cipher, &mut conn).await else { err!("Cipher doesn't exist") }; @@ -792,25 +783,43 @@ async fn post_bulk_collections( err!("Cipher is not write accessible") } - ciphers.push(cipher); - } - - for collection_id in data.collection_ids { - let Some(collection) = Collection::find_by_uuid_and_org(&collection_id, &data.organization_id, &mut conn).await else { - err!("Collection doesn't exist") - }; - - if collection.is_writable_by_user(&headers.user.uuid, &mut conn).await { - for cipher in &ciphers { - if data.remove_collections { - CollectionCipher::delete(&cipher.uuid, &collection.uuid, &mut conn).await?; - } else { - CollectionCipher::save(&cipher.uuid, &collection.uuid, &mut conn).await?; + for collection in &data.collection_ids { + match Collection::find_by_uuid_and_org(collection, &data.organization_id, &mut conn).await { + None => err!("Invalid collection ID provided"), + Some(collection) => { + if collection.is_writable_by_user(&headers.user.uuid, &mut conn).await { + if data.remove_collections { + CollectionCipher::delete(&cipher.uuid, &collection.uuid, &mut conn).await?; + } else { + CollectionCipher::save(&cipher.uuid, &collection.uuid, &mut conn).await?; + } + } else { + err!("No rights to modify the collection") + } } } - } else { - err!("No rights to modify the collection") } + + nt.send_cipher_update( + UpdateType::SyncCipherUpdate, + &cipher, + &cipher.update_users_revision(&mut conn).await, + &headers.device.uuid, + Some(data.collection_ids.clone()), + &mut conn, + ) + .await; + + log_event( + EventType::CipherUpdatedCollections as i32, + &cipher.uuid, + &cipher.organization_uuid.clone().unwrap(), + &headers.user.uuid, + headers.device.atype, + &headers.ip.ip, + &mut conn, + ) + .await; } Ok(())