diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api/core/mod.rs | 2 | ||||
-rw-r--r-- | src/api/core/organizations.rs | 69 | ||||
-rw-r--r-- | src/db/models/cipher.rs | 11 |
3 files changed, 82 insertions, 0 deletions
diff --git a/src/api/core/mod.rs b/src/api/core/mod.rs index 41bd4d6b..b08b6fbe 100644 --- a/src/api/core/mod.rs +++ b/src/api/core/mod.rs @@ -190,6 +190,8 @@ fn config() -> Json<Value> { parse_experimental_client_feature_flags(&crate::CONFIG.experimental_client_feature_flags()); // Force the new key rotation feature feature_states.insert("key-rotation-improvements".to_string(), true); + feature_states.insert("flexible-collections-v-1".to_string(), false); + Json(json!({ // Note: The clients use this version to handle backwards compatibility concerns // This means they expect a version that closely matches the Bitwarden server version diff --git a/src/api/core/organizations.rs b/src/api/core/organizations.rs index 01337311..cbf029b5 100644 --- a/src/api/core/organizations.rs +++ b/src/api/core/organizations.rs @@ -2,6 +2,7 @@ use num_traits::FromPrimitive; use rocket::serde::json::Json; use rocket::Route; use serde_json::Value; +use std::collections::{HashMap, HashSet}; use crate::{ api::{ @@ -39,6 +40,7 @@ pub fn routes() -> Vec<Route> { delete_organization_collection, post_organization_collection_delete, bulk_delete_organization_collections, + post_bulk_collections, get_org_details, get_org_users, send_invite, @@ -1631,6 +1633,66 @@ async fn post_org_import( user.update_revision(&mut conn).await } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +#[allow(dead_code)] +struct BulkCollectionsData { + organization_id: String, + cipher_ids: Vec<String>, + collection_ids: HashSet<String>, + remove_collections: bool, +} + +// This endpoint is only reachable via the organization view, therefor this endpoint is located here +// Also Bitwarden does not send out Notifications for these changes, it only does this for individual cipher collection updates +#[post("/ciphers/bulk-collections", data = "<data>")] +async fn post_bulk_collections(data: Json<BulkCollectionsData>, headers: Headers, mut conn: DbConn) -> EmptyResult { + let data: BulkCollectionsData = data.into_inner(); + + // This feature does not seem to be active on all the clients + // To prevent future issues, add a check to block a call when this is set to true + if data.remove_collections { + err!("Bulk removing of collections is not yet implemented") + } + + // Get all the collection available to the user in one query + // Also filter based upon the provided collections + let user_collections: HashMap<String, Collection> = + Collection::find_by_organization_and_user_uuid(&data.organization_id, &headers.user.uuid, &mut conn) + .await + .into_iter() + .filter_map(|c| { + if data.collection_ids.contains(&c.uuid) { + Some((c.uuid.clone(), c)) + } else { + None + } + }) + .collect(); + + // Verify if all the collections requested exists and are writeable for the user, else abort + for collection_uuid in &data.collection_ids { + match user_collections.get(collection_uuid) { + Some(collection) if collection.is_writable_by_user(&headers.user.uuid, &mut conn).await => (), + _ => err_code!("Resource not found", "User does not have access to a collection", 404), + } + } + + for cipher_id in data.cipher_ids.iter() { + // Only act on existing cipher uuid's + // Do not abort the operation just ignore it, it could be a cipher was just deleted for example + if let Some(cipher) = Cipher::find_by_uuid_and_org(cipher_id, &data.organization_id, &mut conn).await { + if cipher.is_write_accessible_to_user(&headers.user.uuid, &mut conn).await { + for collection in &data.collection_ids { + CollectionCipher::save(&cipher.uuid, collection, &mut conn).await?; + } + } + }; + } + + Ok(()) +} + #[get("/organizations/<org_id>/policies")] async fn list_policies(org_id: &str, _headers: AdminHeaders, mut conn: DbConn) -> Json<Value> { let policies = OrgPolicy::find_by_org(org_id, &mut conn).await; @@ -1645,6 +1707,13 @@ async fn list_policies(org_id: &str, _headers: AdminHeaders, mut conn: DbConn) - #[get("/organizations/<org_id>/policies/token?<token>")] async fn list_policies_token(org_id: &str, token: &str, mut conn: DbConn) -> JsonResult { + // web-vault 2024.6.2 seems to send these values and cause logs to output errors + // Catch this and prevent errors in the logs + // TODO: CleanUp after 2024.6.x is not used anymore. + if org_id == "undefined" && token == "undefined" { + return Ok(Json(json!({}))); + } + let invite = crate::auth::decode_invite(token)?; let invite_org_id = match invite.org_id { diff --git a/src/db/models/cipher.rs b/src/db/models/cipher.rs index c26b4d89..d577971d 100644 --- a/src/db/models/cipher.rs +++ b/src/db/models/cipher.rs @@ -620,6 +620,17 @@ impl Cipher { }} } + pub async fn find_by_uuid_and_org(cipher_uuid: &str, org_uuid: &str, conn: &mut DbConn) -> Option<Self> { + db_run! {conn: { + ciphers::table + .filter(ciphers::uuid.eq(cipher_uuid)) + .filter(ciphers::organization_uuid.eq(org_uuid)) + .first::<CipherDb>(conn) + .ok() + .from_db() + }} + } + // Find all ciphers accessible or visible to the specified user. // // "Accessible" means the user has read access to the cipher, either via |