From 43e2f0d4470884d5ac2717ebd5b466aa0e411a75 Mon Sep 17 00:00:00 2001 From: Stefan Melmuk Date: Mon, 27 May 2024 19:15:53 +0200 Subject: [PATCH] differentiate the /collection endpoints --- src/api/core/ciphers.rs | 63 ++++++++++++++++++++++-- src/db/models/cipher.rs | 106 ++++++++++++++++++++++++++++++++-------- 2 files changed, 144 insertions(+), 25 deletions(-) diff --git a/src/api/core/ciphers.rs b/src/api/core/ciphers.rs index 036bb588..ba24847f 100644 --- a/src/api/core/ciphers.rs +++ b/src/api/core/ciphers.rs @@ -703,7 +703,7 @@ async fn put_collections_update( conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - post_collections_admin(uuid, data, headers, conn, nt).await + post_collections_update(uuid, data, headers, conn, nt).await } #[post("/ciphers//collections", data = "")] @@ -711,10 +711,65 @@ async fn post_collections_update( uuid: &str, data: Json, headers: Headers, - conn: DbConn, + mut conn: DbConn, nt: Notify<'_>, ) -> EmptyResult { - post_collections_admin(uuid, data, headers, conn, nt).await + let data: CollectionsAdminData = data.into_inner(); + + let cipher = match Cipher::find_by_uuid(uuid, &mut conn).await { + Some(cipher) => cipher, + None => err!("Cipher doesn't exist"), + }; + + if !cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { + err!("Cipher is not write accessible") + } + + let posted_collections = HashSet::::from_iter(data.collection_ids); + let current_collections = + HashSet::::from_iter(cipher.get_collections(headers.user.uuid.clone(), &mut conn).await); + + for collection in posted_collections.symmetric_difference(¤t_collections) { + match Collection::find_by_uuid(collection, &mut conn).await { + None => err!("Invalid collection ID provided"), + Some(collection) => { + if collection.is_writable_by_user(&headers.user.uuid, &mut conn).await { + if posted_collections.contains(&collection.uuid) { + // Add to collection + CollectionCipher::save(&cipher.uuid, &collection.uuid, &mut conn).await?; + } else { + // Remove from collection + CollectionCipher::delete(&cipher.uuid, &collection.uuid, &mut conn).await?; + } + } 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(Vec::from_iter(posted_collections)), + &mut conn, + ) + .await; + + log_event( + EventType::CipherUpdatedCollections as i32, + &cipher.uuid, + &cipher.organization_uuid.unwrap(), + &headers.user.uuid, + headers.device.atype, + &headers.ip.ip, + &mut conn, + ) + .await; + + Ok(()) } #[put("/ciphers//collections-admin", data = "")] @@ -749,7 +804,7 @@ async fn post_collections_admin( let posted_collections = HashSet::::from_iter(data.collection_ids); let current_collections = - HashSet::::from_iter(cipher.get_collections(headers.user.uuid.clone(), &mut conn).await); + HashSet::::from_iter(cipher.get_admin_collections(headers.user.uuid.clone(), &mut conn).await); for collection in posted_collections.symmetric_difference(¤t_collections) { match Collection::find_by_uuid(collection, &mut conn).await { diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index 03a1f20f..7e119a7c 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -206,7 +206,7 @@ impl Cipher { Cow::from(Vec::with_capacity(0)) } } else { - Cow::from(self.get_collections(user_uuid.to_string(), conn).await) + Cow::from(self.get_admin_collections(user_uuid.to_string(), conn).await) }; // There are three types of cipher response models in upstream @@ -776,6 +776,7 @@ impl Cipher { if CONFIG.org_groups_enabled() { db_run! {conn: { ciphers_collections::table + .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) .inner_join(collections::table.on( collections::uuid.eq(ciphers_collections::collection_uuid) )) @@ -795,11 +796,12 @@ impl Cipher { collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid) .and(collections_groups::groups_uuid.eq(groups::uuid)) )) - .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) .filter(users_organizations::access_all.eq(true) // User has access all - .or(users_collections::user_uuid.eq(user_id)) // User has access to collection + .or(users_collections::user_uuid.eq(user_id) // User has access to collection + .and(users_collections::read_only.eq(false))) .or(groups::access_all.eq(true)) // Access via groups - .or(collections_groups::collections_uuid.is_not_null()) // Access via groups + .or(collections_groups::collections_uuid.is_not_null() // Access via groups + .and(collections_groups::read_only.eq(false))) ) .select(ciphers_collections::collection_uuid) .load::(conn).unwrap_or_default() @@ -807,23 +809,85 @@ impl Cipher { } else { db_run! {conn: { ciphers_collections::table - .inner_join(collections::table.on( - collections::uuid.eq(ciphers_collections::collection_uuid) - )) - .inner_join(users_organizations::table.on( - users_organizations::org_uuid.eq(collections::org_uuid) - .and(users_organizations::user_uuid.eq(user_id.clone())) - )) - .left_join(users_collections::table.on( - users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) - .and(users_collections::user_uuid.eq(user_id.clone())) - )) - .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) - .filter(users_organizations::access_all.eq(true) // User has access all - .or(users_collections::user_uuid.eq(user_id)) // User has access to collection - ) - .select(ciphers_collections::collection_uuid) - .load::(conn).unwrap_or_default() + .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) + .inner_join(collections::table.on( + collections::uuid.eq(ciphers_collections::collection_uuid) + )) + .inner_join(users_organizations::table.on( + users_organizations::org_uuid.eq(collections::org_uuid) + .and(users_organizations::user_uuid.eq(user_id.clone())) + )) + .left_join(users_collections::table.on( + users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) + .and(users_collections::user_uuid.eq(user_id.clone())) + )) + .filter(users_organizations::access_all.eq(true) // User has access all + .or(users_collections::user_uuid.eq(user_id) // User has access to collection + .and(users_collections::read_only.eq(false))) + ) + .select(ciphers_collections::collection_uuid) + .load::(conn).unwrap_or_default() + }} + } + } + + pub async fn get_admin_collections(&self, user_id: String, conn: &mut DbConn) -> Vec { + if CONFIG.org_groups_enabled() { + db_run! {conn: { + ciphers_collections::table + .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) + .inner_join(collections::table.on( + collections::uuid.eq(ciphers_collections::collection_uuid) + )) + .left_join(users_organizations::table.on( + users_organizations::org_uuid.eq(collections::org_uuid) + .and(users_organizations::user_uuid.eq(user_id.clone())) + )) + .left_join(users_collections::table.on( + users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) + .and(users_collections::user_uuid.eq(user_id.clone())) + )) + .left_join(groups_users::table.on( + groups_users::users_organizations_uuid.eq(users_organizations::uuid) + )) + .left_join(groups::table.on(groups::uuid.eq(groups_users::groups_uuid))) + .left_join(collections_groups::table.on( + collections_groups::collections_uuid.eq(ciphers_collections::collection_uuid) + .and(collections_groups::groups_uuid.eq(groups::uuid)) + )) + .filter(users_organizations::access_all.eq(true) // User has access all + .or(users_collections::user_uuid.eq(user_id) // User has access to collection + .and(users_collections::read_only.eq(false))) + .or(groups::access_all.eq(true)) // Access via groups + .or(collections_groups::collections_uuid.is_not_null() // Access via groups + .and(collections_groups::read_only.eq(false))) + .or(users_organizations::atype.le(UserOrgType::Admin as i32)) // User is admin or owner + ) + .select(ciphers_collections::collection_uuid) + .load::(conn).unwrap_or_default() + }} + } else { + db_run! {conn: { + ciphers_collections::table + .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) + .inner_join(collections::table.on( + collections::uuid.eq(ciphers_collections::collection_uuid) + )) + .inner_join(users_organizations::table.on( + users_organizations::org_uuid.eq(collections::org_uuid) + .and(users_organizations::user_uuid.eq(user_id.clone())) + )) + .left_join(users_collections::table.on( + users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) + .and(users_collections::user_uuid.eq(user_id.clone())) + )) + .filter(users_organizations::access_all.eq(true) // User has access all + .or(users_collections::user_uuid.eq(user_id) // User has access to collection + .and(users_collections::read_only.eq(false))) + .or(users_organizations::atype.le(UserOrgType::Admin as i32)) // User is admin or owner + ) + .select(ciphers_collections::collection_uuid) + .load::(conn).unwrap_or_default() }} } }