collection.rs (23766B)
1 use super::{User, UserOrgStatus, UserOrgType, UserOrganization}; 2 use crate::api::core::CipherSyncData; 3 use crate::util; 4 use diesel::result::{self, DatabaseErrorKind}; 5 use serde_json::Value; 6 7 db_object! { 8 #[derive(AsChangeset, Insertable, Queryable)] 9 #[diesel(table_name = collections)] 10 pub struct Collection { 11 pub uuid: String, 12 pub org_uuid: String, 13 pub name: String, 14 pub external_id: Option<String>, 15 } 16 17 #[derive(Insertable, Queryable)] 18 #[diesel(table_name = users_collections)] 19 pub struct CollectionUser { 20 pub user_uuid: String, 21 pub collection_uuid: String, 22 pub read_only: bool, 23 pub hide_passwords: bool, 24 } 25 26 #[derive(Insertable)] 27 #[diesel(table_name = ciphers_collections)] 28 pub struct CollectionCipher { 29 cipher_uuid: String, 30 collection_uuid: String, 31 } 32 } 33 34 /// Local methods 35 impl Collection { 36 pub fn new(org_uuid: String, name: String, external_id: Option<String>) -> Self { 37 let mut new_model = Self { 38 uuid: util::get_uuid(), 39 org_uuid, 40 name, 41 external_id: None, 42 }; 43 new_model.set_external_id(external_id); 44 new_model 45 } 46 47 pub fn to_json(&self) -> Value { 48 json!({ 49 "externalId": self.external_id, 50 "id": self.uuid, 51 "organizationId": self.org_uuid, 52 "name": self.name, 53 "object": "collection", 54 }) 55 } 56 57 fn set_external_id(&mut self, external_id: Option<String>) { 58 //Check if external id is empty. We don't want to have 59 //empty strings in the database 60 match external_id { 61 Some(external_id) => { 62 if external_id.is_empty() { 63 self.external_id = None; 64 } else { 65 self.external_id = Some(external_id); 66 } 67 } 68 None => self.external_id = None, 69 } 70 } 71 72 pub async fn to_json_details( 73 &self, 74 user_uuid: &str, 75 cipher_sync_data: Option<&CipherSyncData>, 76 conn: &DbConn, 77 ) -> Value { 78 let (read_only, hide_passwords, can_manage) = if let Some(cipher_sync_data) = 79 cipher_sync_data 80 { 81 match cipher_sync_data.user_organizations.get(&self.org_uuid) { 82 // Only for Manager types Bitwarden returns true for the can_manage option 83 // Owners and Admins always have true 84 Some(uo) if uo.has_full_access() => { 85 (false, false, uo.atype >= UserOrgType::Manager) 86 } 87 Some(uo) => { 88 // Only let a manager manage collections when the have full read/write access 89 let is_manager = uo.atype == UserOrgType::Manager; 90 cipher_sync_data.user_collections.get(&self.uuid).map_or( 91 (false, false, false), 92 |uc| { 93 ( 94 uc.read_only, 95 uc.hide_passwords, 96 is_manager && !uc.read_only && !uc.hide_passwords, 97 ) 98 }, 99 ) 100 } 101 _ => (true, true, false), 102 } 103 } else { 104 match UserOrganization::find_confirmed_by_user_and_org(user_uuid, &self.org_uuid, conn) 105 .await 106 { 107 Some(ou) if ou.has_full_access() => { 108 (false, false, ou.atype >= UserOrgType::Manager) 109 } 110 Some(ou) => { 111 let is_manager = ou.atype == UserOrgType::Manager; 112 let read_only = !self.is_writable_by_user(user_uuid, conn).await; 113 let hide_passwords = self.hide_passwords_for_user(user_uuid, conn).await; 114 ( 115 read_only, 116 hide_passwords, 117 is_manager && !read_only && !hide_passwords, 118 ) 119 } 120 _ => ( 121 !self.is_writable_by_user(user_uuid, conn).await, 122 self.hide_passwords_for_user(user_uuid, conn).await, 123 false, 124 ), 125 } 126 }; 127 128 let mut json_object = self.to_json(); 129 json_object["object"] = json!("collectionDetails"); 130 json_object["readOnly"] = json!(read_only); 131 json_object["hidePasswords"] = json!(hide_passwords); 132 json_object["manage"] = json!(can_manage); 133 json_object 134 } 135 136 pub async fn can_access_collection( 137 org_user: &UserOrganization, 138 col_id: &str, 139 conn: &DbConn, 140 ) -> bool { 141 org_user.has_status(UserOrgStatus::Confirmed) 142 && (org_user.has_full_access() 143 || CollectionUser::has_access_to_collection_by_user( 144 col_id, 145 &org_user.user_uuid, 146 conn, 147 ) 148 .await) 149 } 150 } 151 152 use crate::api::EmptyResult; 153 use crate::db::DbConn; 154 use crate::error::MapResult; 155 156 /// Database methods 157 impl Collection { 158 pub async fn save(&self, conn: &DbConn) -> EmptyResult { 159 self.update_users_revision(conn).await; 160 db_run! { conn: 161 { 162 match diesel::replace_into(collections::table) 163 .values(CollectionDb::to_db(self)) 164 .execute(conn) 165 { 166 Ok(_) => Ok(()), 167 // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. 168 Err(result::Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _)) => { 169 diesel::update(collections::table) 170 .filter(collections::uuid.eq(&self.uuid)) 171 .set(CollectionDb::to_db(self)) 172 .execute(conn) 173 .map_res("Error saving collection") 174 } 175 Err(e) => Err(e.into()), 176 }.map_res("Error saving collection") 177 } 178 } 179 } 180 181 pub async fn delete(self, conn: &DbConn) -> EmptyResult { 182 self.update_users_revision(conn).await; 183 CollectionCipher::delete_all_by_collection(&self.uuid, conn).await?; 184 CollectionUser::delete_all_by_collection(&self.uuid, conn).await?; 185 186 db_run! { conn: { 187 diesel::delete(collections::table.filter(collections::uuid.eq(self.uuid))) 188 .execute(conn) 189 .map_res("Error deleting collection") 190 }} 191 } 192 193 pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { 194 for collection in Self::find_by_organization(org_uuid, conn).await { 195 collection.delete(conn).await?; 196 } 197 Ok(()) 198 } 199 200 async fn update_users_revision(&self, conn: &DbConn) { 201 for user_org in 202 &UserOrganization::find_by_collection_and_org(&self.uuid, &self.org_uuid, conn).await 203 { 204 User::update_uuid_revision(&user_org.user_uuid, conn).await; 205 } 206 } 207 208 pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { 209 db_run! { conn: { 210 collections::table 211 .filter(collections::uuid.eq(uuid)) 212 .first::<CollectionDb>(conn) 213 .ok() 214 .from_db() 215 }} 216 } 217 218 pub async fn find_by_user_uuid(user_uuid: String, conn: &DbConn) -> Vec<Self> { 219 db_run! { conn: { 220 collections::table 221 .left_join(users_collections::table.on( 222 users_collections::collection_uuid.eq(collections::uuid).and( 223 users_collections::user_uuid.eq(user_uuid.clone()) 224 ) 225 )) 226 .left_join(users_organizations::table.on( 227 collections::org_uuid.eq(users_organizations::org_uuid).and( 228 users_organizations::user_uuid.eq(user_uuid.clone()) 229 ) 230 )) 231 .filter( 232 users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)) 233 ) 234 .filter( 235 users_collections::user_uuid.eq(user_uuid).or( // Directly accessed collection 236 users_organizations::access_all.eq(true) // access_all in Organization 237 ) 238 ) 239 .select(collections::all_columns) 240 .distinct() 241 .load::<CollectionDb>(conn).expect("Error loading collections").from_db() 242 }} 243 } 244 245 pub async fn find_by_organization_and_user_uuid( 246 org_uuid: &str, 247 user_uuid: &str, 248 conn: &DbConn, 249 ) -> Vec<Self> { 250 Self::find_by_user_uuid(user_uuid.to_owned(), conn) 251 .await 252 .into_iter() 253 .filter(|c| c.org_uuid == org_uuid) 254 .collect() 255 } 256 257 pub async fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> { 258 db_run! { conn: { 259 collections::table 260 .filter(collections::org_uuid.eq(org_uuid)) 261 .load::<CollectionDb>(conn) 262 .expect("Error loading collections") 263 .from_db() 264 }} 265 } 266 267 pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> u64 { 268 u64::try_from({ 269 db_run! { conn: { 270 collections::table 271 .filter(collections::org_uuid.eq(org_uuid)) 272 .count() 273 .first::<i64>(conn) 274 .ok() 275 .unwrap_or(0) 276 }} 277 }) 278 .expect("underflow") 279 } 280 281 pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> { 282 db_run! { conn: { 283 collections::table 284 .filter(collections::uuid.eq(uuid)) 285 .filter(collections::org_uuid.eq(org_uuid)) 286 .select(collections::all_columns) 287 .first::<CollectionDb>(conn) 288 .ok() 289 .from_db() 290 }} 291 } 292 293 pub async fn find_by_uuid_and_user( 294 uuid: &str, 295 user_uuid: String, 296 conn: &DbConn, 297 ) -> Option<Self> { 298 db_run! { conn: { 299 collections::table 300 .left_join(users_collections::table.on( 301 users_collections::collection_uuid.eq(collections::uuid).and( 302 users_collections::user_uuid.eq(user_uuid.clone()) 303 ) 304 )) 305 .left_join(users_organizations::table.on( 306 collections::org_uuid.eq(users_organizations::org_uuid).and( 307 users_organizations::user_uuid.eq(user_uuid) 308 ) 309 )) 310 .filter(collections::uuid.eq(uuid)) 311 .filter( 312 users_collections::collection_uuid.eq(uuid).or( // Directly accessed collection 313 users_organizations::access_all.eq(true).or( // access_all in Organization 314 users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin or owner 315 ))).select(collections::all_columns) 316 .first::<CollectionDb>(conn).ok() 317 .from_db() 318 }} 319 } 320 321 pub async fn is_writable_by_user(&self, user_uuid: &str, conn: &DbConn) -> bool { 322 let user_uuid = user_uuid.to_owned(); 323 db_run! { conn: { 324 collections::table 325 .filter(collections::uuid.eq(&self.uuid)) 326 .inner_join(users_organizations::table.on( 327 collections::org_uuid.eq(users_organizations::org_uuid) 328 .and(users_organizations::user_uuid.eq(user_uuid.clone())) 329 )) 330 .left_join(users_collections::table.on( 331 users_collections::collection_uuid.eq(collections::uuid) 332 .and(users_collections::user_uuid.eq(user_uuid)) 333 )) 334 .filter(users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin or owner 335 .or(users_organizations::access_all.eq(true)) // access_all via membership 336 .or(users_collections::collection_uuid.eq(&self.uuid) // write access given to collection 337 .and(users_collections::read_only.eq(false))) 338 ) 339 .count() 340 .first::<i64>(conn) 341 .ok() 342 .unwrap_or(0) != 0 343 }} 344 } 345 346 async fn hide_passwords_for_user(&self, user_uuid: &str, conn: &DbConn) -> bool { 347 let user_uuid = user_uuid.to_owned(); 348 db_run! { conn: { 349 collections::table 350 .left_join(users_collections::table.on( 351 users_collections::collection_uuid.eq(collections::uuid).and( 352 users_collections::user_uuid.eq(user_uuid.clone()) 353 ) 354 )) 355 .left_join(users_organizations::table.on( 356 collections::org_uuid.eq(users_organizations::org_uuid).and( 357 users_organizations::user_uuid.eq(user_uuid) 358 ) 359 )) 360 .filter(collections::uuid.eq(&self.uuid)) 361 .filter( 362 users_collections::collection_uuid.eq(&self.uuid).and(users_collections::hide_passwords.eq(true)).or(// Directly accessed collection 363 users_organizations::access_all.eq(true).or( // access_all in Organization 364 users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin or owner 365 )) 366 ) 367 .count() 368 .first::<i64>(conn) 369 .ok() 370 .unwrap_or(0) != 0 371 }} 372 } 373 } 374 375 /// Database methods 376 impl CollectionUser { 377 pub async fn find_by_organization_and_user_uuid( 378 org_uuid: &str, 379 user_uuid: &str, 380 conn: &DbConn, 381 ) -> Vec<Self> { 382 db_run! { conn: { 383 users_collections::table 384 .filter(users_collections::user_uuid.eq(user_uuid)) 385 .inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid))) 386 .filter(collections::org_uuid.eq(org_uuid)) 387 .select(users_collections::all_columns) 388 .load::<CollectionUserDb>(conn) 389 .expect("Error loading users_collections") 390 .from_db() 391 }} 392 } 393 394 pub async fn find_by_organization(org_uuid: &str, conn: &DbConn) -> Vec<Self> { 395 db_run! { conn: { 396 users_collections::table 397 .inner_join(collections::table.on(collections::uuid.eq(users_collections::collection_uuid))) 398 .filter(collections::org_uuid.eq(org_uuid)) 399 .inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid))) 400 .select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords)) 401 .load::<CollectionUserDb>(conn) 402 .expect("Error loading users_collections") 403 .from_db() 404 }} 405 } 406 407 pub async fn save( 408 user_uuid: &str, 409 collection_uuid: &str, 410 read_only: bool, 411 hide_passwords: bool, 412 conn: &DbConn, 413 ) -> EmptyResult { 414 User::update_uuid_revision(user_uuid, conn).await; 415 db_run! { conn: 416 { 417 match diesel::replace_into(users_collections::table) 418 .values(( 419 users_collections::user_uuid.eq(user_uuid), 420 users_collections::collection_uuid.eq(collection_uuid), 421 users_collections::read_only.eq(read_only), 422 users_collections::hide_passwords.eq(hide_passwords), 423 )) 424 .execute(conn) 425 { 426 Ok(_) => Ok(()), 427 // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. 428 Err(result::Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _)) => { 429 diesel::update(users_collections::table) 430 .filter(users_collections::user_uuid.eq(user_uuid)) 431 .filter(users_collections::collection_uuid.eq(collection_uuid)) 432 .set(( 433 users_collections::user_uuid.eq(user_uuid), 434 users_collections::collection_uuid.eq(collection_uuid), 435 users_collections::read_only.eq(read_only), 436 users_collections::hide_passwords.eq(hide_passwords), 437 )) 438 .execute(conn) 439 .map_res("Error adding user to collection") 440 } 441 Err(e) => Err(e.into()), 442 }.map_res("Error adding user to collection") 443 } 444 } 445 } 446 447 pub async fn delete(self, conn: &DbConn) -> EmptyResult { 448 User::update_uuid_revision(&self.user_uuid, conn).await; 449 db_run! { conn: { 450 diesel::delete( 451 users_collections::table 452 .filter(users_collections::user_uuid.eq(&self.user_uuid)) 453 .filter(users_collections::collection_uuid.eq(&self.collection_uuid)), 454 ) 455 .execute(conn) 456 .map_res("Error removing user from collection") 457 }} 458 } 459 460 pub async fn find_by_collection(collection_uuid: &str, conn: &DbConn) -> Vec<Self> { 461 db_run! { conn: { 462 users_collections::table 463 .filter(users_collections::collection_uuid.eq(collection_uuid)) 464 .select(users_collections::all_columns) 465 .load::<CollectionUserDb>(conn) 466 .expect("Error loading users_collections") 467 .from_db() 468 }} 469 } 470 471 pub async fn find_by_collection_swap_user_uuid_with_org_user_uuid( 472 collection_uuid: &str, 473 conn: &DbConn, 474 ) -> Vec<Self> { 475 db_run! { conn: { 476 users_collections::table 477 .filter(users_collections::collection_uuid.eq(collection_uuid)) 478 .inner_join(users_organizations::table.on(users_organizations::user_uuid.eq(users_collections::user_uuid))) 479 .select((users_organizations::uuid, users_collections::collection_uuid, users_collections::read_only, users_collections::hide_passwords)) 480 .load::<CollectionUserDb>(conn) 481 .expect("Error loading users_collections") 482 .from_db() 483 }} 484 } 485 486 pub async fn find_by_collection_and_user( 487 collection_uuid: &str, 488 user_uuid: &str, 489 conn: &DbConn, 490 ) -> Option<Self> { 491 db_run! { conn: { 492 users_collections::table 493 .filter(users_collections::collection_uuid.eq(collection_uuid)) 494 .filter(users_collections::user_uuid.eq(user_uuid)) 495 .select(users_collections::all_columns) 496 .first::<CollectionUserDb>(conn) 497 .ok() 498 .from_db() 499 }} 500 } 501 502 pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { 503 db_run! { conn: { 504 users_collections::table 505 .filter(users_collections::user_uuid.eq(user_uuid)) 506 .select(users_collections::all_columns) 507 .load::<CollectionUserDb>(conn) 508 .expect("Error loading users_collections") 509 .from_db() 510 }} 511 } 512 513 pub async fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { 514 for collection in &Self::find_by_collection(collection_uuid, conn).await { 515 User::update_uuid_revision(&collection.user_uuid, conn).await; 516 } 517 db_run! { conn: { 518 diesel::delete(users_collections::table.filter(users_collections::collection_uuid.eq(collection_uuid))) 519 .execute(conn) 520 .map_res("Error deleting users from collection") 521 }} 522 } 523 524 pub async fn delete_all_by_user_and_org( 525 user_uuid: &str, 526 org_uuid: &str, 527 conn: &DbConn, 528 ) -> EmptyResult { 529 let collectionusers = 530 Self::find_by_organization_and_user_uuid(org_uuid, user_uuid, conn).await; 531 db_run! { conn: { 532 for user in collectionusers { 533 let _: () = diesel::delete(users_collections::table.filter( 534 users_collections::user_uuid.eq(user_uuid) 535 .and(users_collections::collection_uuid.eq(user.collection_uuid)) 536 )) 537 .execute(conn) 538 .map_res("Error removing user from collections")?; 539 } 540 Ok(()) 541 }} 542 } 543 544 pub async fn has_access_to_collection_by_user( 545 col_id: &str, 546 user_uuid: &str, 547 conn: &DbConn, 548 ) -> bool { 549 Self::find_by_collection_and_user(col_id, user_uuid, conn) 550 .await 551 .is_some() 552 } 553 } 554 555 /// Database methods 556 impl CollectionCipher { 557 pub async fn save(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult { 558 Self::update_users_revision(collection_uuid, conn).await; 559 db_run! { conn: 560 { 561 // Not checking for ForeignKey Constraints here. 562 // Table ciphers_collections does not have ForeignKey Constraints which would cause conflicts. 563 // This table has no constraints pointing to itself, but only to others. 564 diesel::replace_into(ciphers_collections::table) 565 .values(( 566 ciphers_collections::cipher_uuid.eq(cipher_uuid), 567 ciphers_collections::collection_uuid.eq(collection_uuid), 568 )) 569 .execute(conn) 570 .map_res("Error adding cipher to collection") 571 } 572 } 573 } 574 575 pub async fn delete(cipher_uuid: &str, collection_uuid: &str, conn: &DbConn) -> EmptyResult { 576 Self::update_users_revision(collection_uuid, conn).await; 577 578 db_run! { conn: { 579 diesel::delete( 580 ciphers_collections::table 581 .filter(ciphers_collections::cipher_uuid.eq(cipher_uuid)) 582 .filter(ciphers_collections::collection_uuid.eq(collection_uuid)), 583 ) 584 .execute(conn) 585 .map_res("Error deleting cipher from collection") 586 }} 587 } 588 589 pub async fn delete_all_by_cipher(cipher_uuid: &str, conn: &DbConn) -> EmptyResult { 590 db_run! { conn: { 591 diesel::delete(ciphers_collections::table.filter(ciphers_collections::cipher_uuid.eq(cipher_uuid))) 592 .execute(conn) 593 .map_res("Error removing cipher from collections") 594 }} 595 } 596 597 async fn delete_all_by_collection(collection_uuid: &str, conn: &DbConn) -> EmptyResult { 598 db_run! { conn: { 599 diesel::delete(ciphers_collections::table.filter(ciphers_collections::collection_uuid.eq(collection_uuid))) 600 .execute(conn) 601 .map_res("Error removing ciphers from collection") 602 }} 603 } 604 605 async fn update_users_revision(collection_uuid: &str, conn: &DbConn) { 606 if let Some(collection) = Collection::find_by_uuid(collection_uuid, conn).await { 607 collection.update_users_revision(conn).await; 608 } 609 } 610 }