cipher.rs (33258B)
1 use super::{ 2 CollectionCipher, Favorite, FolderCipher, User, UserOrgStatus, UserOrgType, UserOrganization, 3 }; 4 use crate::api::core::{CipherData, CipherSyncData, CipherSyncType}; 5 use crate::util::{self, LowerCase}; 6 use chrono::{DateTime, NaiveDateTime, Utc}; 7 use diesel::result::{self, DatabaseErrorKind}; 8 use serde_json::Value; 9 use std::borrow::Cow; 10 use std::string::ToString; 11 12 db_object! { 13 #[derive(AsChangeset, Insertable, Queryable)] 14 #[diesel(table_name = ciphers)] 15 #[diesel(treat_none_as_null = true)] 16 pub struct Cipher { 17 pub uuid: String, 18 created_at: NaiveDateTime, 19 pub updated_at: NaiveDateTime, 20 pub user_uuid: Option<String>, 21 pub organization_uuid: Option<String>, 22 pub key: Option<String>, 23 atype: i32, 24 pub name: String, 25 pub notes: Option<String>, 26 pub fields: Option<String>, 27 pub data: String, 28 pub password_history: Option<String>, 29 pub deleted_at: Option<NaiveDateTime>, 30 pub reprompt: Option<i32>, 31 } 32 } 33 34 enum RepromptType { 35 None = 0, 36 } 37 impl From<RepromptType> for i32 { 38 fn from(value: RepromptType) -> Self { 39 match value { 40 RepromptType::None => 0i32, 41 } 42 } 43 } 44 45 /// Local methods 46 impl Cipher { 47 pub fn new(atype: i32, name: String) -> Self { 48 let now = Utc::now().naive_utc(); 49 Self { 50 uuid: util::get_uuid(), 51 created_at: now, 52 updated_at: now, 53 user_uuid: None, 54 organization_uuid: None, 55 key: None, 56 atype, 57 name, 58 notes: None, 59 fields: None, 60 data: String::new(), 61 password_history: None, 62 deleted_at: None, 63 reprompt: None, 64 } 65 } 66 pub fn validate_cipher_data(cipher_data: &[CipherData]) -> EmptyResult { 67 let mut validation_errors = serde_json::Map::new(); 68 for (index, cipher) in cipher_data.iter().enumerate() { 69 if let Some(ref note) = cipher.notes { 70 if note.len() > 10_000 { 71 validation_errors.insert( 72 format!("Ciphers[{index}].Notes"), 73 serde_json::to_value([ 74 "The field Notes exceeds the maximum encrypted value length of 10000 characters.", 75 ]) 76 .unwrap(), 77 ); 78 } 79 } 80 // Validate the password history if it contains `null` values and if so, return a warning 81 if let Some(Value::Array(ref password_history)) = cipher.password_history { 82 for pwh in password_history { 83 if let Value::Object(ref pwo) = *pwh { 84 if pwo.get("password").is_some_and(|p| !p.is_string()) { 85 validation_errors.insert( 86 format!("Ciphers[{index}].Notes"), 87 serde_json::to_value([ 88 "The password history contains a `null` value. Only strings are allowed.", 89 ]) 90 .unwrap(), 91 ); 92 break; 93 } 94 } 95 } 96 } 97 } 98 if !validation_errors.is_empty() { 99 let err_json = json!({ 100 "message": "The model state is invalid.", 101 "validationErrors" : validation_errors, 102 "object": "error" 103 }); 104 err_json!(err_json, "Import validation errors") 105 } 106 Ok(()) 107 } 108 } 109 use crate::api::EmptyResult; 110 use crate::db::DbConn; 111 use crate::error::MapResult; 112 /// Database methods 113 impl Cipher { 114 pub async fn to_json( 115 &self, 116 user_uuid: &str, 117 cipher_sync_data: Option<&CipherSyncData>, 118 sync_type: CipherSyncType, 119 conn: &DbConn, 120 ) -> Value { 121 // We don't need these values at all for Organizational syncs 122 // Skip any other database calls if this is the case and just return false. 123 let (read_only, hide_passwords) = if sync_type == CipherSyncType::User { 124 match self 125 .get_access_restrictions(user_uuid, cipher_sync_data, conn) 126 .await 127 { 128 Some((ro, hp)) => (ro, hp), 129 None => (true, true), 130 } 131 } else { 132 (false, false) 133 }; 134 let fields_json = self 135 .fields 136 .as_ref() 137 .and_then(|s| { 138 serde_json::from_str::<Vec<LowerCase<Value>>>(s) 139 .inspect_err(|e| warn!("Error parsing fields {e:?} for {}", self.uuid)) 140 .ok() 141 }) 142 .map_or(Value::Null, |d| { 143 d.into_iter() 144 .map(|mut da| { 145 match da.data.get("type") { 146 None => { 147 da.data["type"] = json!(1i32); 148 } 149 Some(x) => { 150 if x.is_string() { 151 let type_num = x 152 .as_str() 153 .unwrap_or_else(|| { 154 unreachable!("there is a bug in Value::is_string") 155 }) 156 .parse::<u8>() 157 .unwrap_or(1); 158 da.data["type"] = json!(type_num); 159 } 160 } 161 } 162 da.data 163 }) 164 .collect() 165 }); 166 let password_history_json = self 167 .password_history 168 .as_ref() 169 .and_then(|s| { 170 serde_json::from_str::<Vec<LowerCase<Value>>>(s) 171 .inspect_err(|e| { 172 warn!("Error parsing password history {e:?} for {}", self.uuid); 173 }) 174 .ok() 175 }) 176 .map_or(Value::Null, |d| { 177 // Check every password history item if they are valid and return it. 178 // If a password field has the type `null` skip it, it breaks newer Bitwarden clients 179 // A second check is done to verify the lastUsedDate exists and is a string, if not the epoch start time will be used 180 d.into_iter() 181 .filter_map(|da| match da.data.get("password") { 182 Some(p) if p.is_string() => Some(da.data), 183 _ => None, 184 }) 185 .map(|da| match da.get("lastUsedDate").and_then(|l| l.as_str()) { 186 Some(l) if DateTime::parse_from_rfc3339(l).is_ok() => da, 187 _ => { 188 let mut dat = da; 189 dat["lastUsedDate"] = json!("1970-01-01T00:00:00.000Z"); 190 dat 191 } 192 }) 193 .collect() 194 }); 195 // Get the type_data or a default to an empty json object '{}'. 196 // If not passing an empty object, mobile clients will crash. 197 let mut type_data_json = serde_json::from_str::<LowerCase<Value>>(&self.data).map_or_else( 198 |_e| { 199 warn!("Error parsing data field for {}", self.uuid); 200 Value::Object(serde_json::Map::new()) 201 }, 202 |d| d.data, 203 ); 204 // NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream 205 // Set the first element of the Uris array as Uri, this is needed several (mobile) clients. 206 if self.atype == 1i32 { 207 if type_data_json["uris"].is_array() { 208 let uri = type_data_json["uris"][0]["uri"].clone(); 209 type_data_json["uri"] = uri; 210 } else { 211 // Upstream always has an Uri key/value 212 type_data_json["uri"] = Value::Null; 213 } 214 } 215 if self.atype == 2i32 { 216 match type_data_json { 217 Value::Object(ref t) if t.get("type").is_some_and(Value::is_number) => {} 218 Value::Null 219 | Value::Bool(_) 220 | Value::Number(_) 221 | Value::String(_) 222 | Value::Array(_) 223 | Value::Object(_) => { 224 type_data_json = json!({"type": 0i32}); 225 } 226 } 227 } 228 // Clone the type_data and add some default value. 229 let mut data_json = type_data_json.clone(); 230 // NOTE: This was marked as *Backwards Compatibility Code*, but as of January 2021 this is still being used by upstream 231 // data_json should always contain the following keys with every atype 232 data_json["fields"] = fields_json.clone(); 233 data_json["name"] = json!(self.name); 234 data_json["notes"] = json!(self.notes); 235 data_json["passwordHistory"] = password_history_json.clone(); 236 let collection_ids = if let Some(cipher_sync_data) = cipher_sync_data { 237 cipher_sync_data 238 .cipher_collections 239 .get(&self.uuid) 240 .map_or_else(|| Cow::from(Vec::with_capacity(0)), Cow::from) 241 } else { 242 Cow::from(self.get_admin_collections(user_uuid.to_owned(), conn).await) 243 }; 244 // There are three types of cipher response models in upstream 245 // Bitwarden: "cipherMini", "cipher", and "cipherDetails" (in order 246 // of increasing level of detail). vaultwarden currently only 247 // supports the "cipherDetails" type, though it seems like the 248 // Bitwarden clients will ignore extra fields. 249 // 250 // Ref: https://github.com/bitwarden/server/blob/master/src/Core/Models/Api/Response/CipherResponseModel.cs 251 let mut json_object = json!({ 252 "object": "cipherDetails", 253 "id": self.uuid, 254 "type": self.atype, 255 "creationDate": util::format_date(&self.created_at), 256 "revisionDate": util::format_date(&self.updated_at), 257 "deletedDate": self.deleted_at.map_or(Value::Null, |d| Value::String(util::format_date(&d))), 258 "reprompt": self.reprompt.unwrap_or_else(|| i32::from(RepromptType::None)), 259 "organizationId": self.organization_uuid, 260 "key": self.key, 261 "attachments": Value::Null, 262 // We have UseTotp set to true by default within the Organization model. 263 // This variable together with UsersGetPremium is used to show or hide the TOTP counter. 264 "organizationUseTotp": true, 265 // This field is specific to the cipherDetails type. 266 "collectionIds": collection_ids, 267 "name": self.name, 268 "notes": self.notes, 269 "fields": fields_json, 270 "data": data_json, 271 "passwordHistory": password_history_json, 272 // All Cipher types are included by default as null, but only the matching one will be populated 273 "login": null, 274 "secureNote": null, 275 "card": null, 276 "identity": null, 277 }); 278 // These values are only needed for user/default syncs 279 // Not during an organizational sync like `get_org_details` 280 // Skip adding these fields in that case 281 if sync_type == CipherSyncType::User { 282 json_object["folderId"] = json!(if let Some(cipher_sync_data) = cipher_sync_data { 283 cipher_sync_data 284 .cipher_folders 285 .get(&self.uuid) 286 .map(ToString::to_string) 287 } else { 288 self.get_folder_uuid(user_uuid, conn).await 289 }); 290 json_object["favorite"] = json!(if let Some(cipher_sync_data) = cipher_sync_data { 291 cipher_sync_data.cipher_favorites.contains(&self.uuid) 292 } else { 293 self.is_favorite(user_uuid, conn).await 294 }); 295 // These values are true by default, but can be false if the 296 // cipher belongs to a collection or group where the org owner has enabled 297 // the "Read Only" or "Hide Passwords" restrictions for the user. 298 json_object["edit"] = json!(!read_only); 299 json_object["viewPassword"] = json!(!hide_passwords); 300 } 301 302 let key = match self.atype { 303 1i32 => "login", 304 2i32 => "secureNote", 305 3i32 => "card", 306 4i32 => "identity", 307 _ => panic!("Wrong type"), 308 }; 309 310 json_object[key] = type_data_json; 311 json_object 312 } 313 314 pub async fn update_users_revision(&self, conn: &DbConn) -> Vec<String> { 315 let mut user_uuids = Vec::new(); 316 match self.user_uuid { 317 Some(ref user_uuid) => { 318 User::update_uuid_revision(user_uuid, conn).await; 319 user_uuids.push(user_uuid.clone()); 320 } 321 None => { 322 // Belongs to Organization, need to update affected users 323 if let Some(ref org_uuid) = self.organization_uuid { 324 for user_org in 325 &UserOrganization::find_by_cipher_and_org(&self.uuid, org_uuid, conn).await 326 { 327 User::update_uuid_revision(&user_org.user_uuid, conn).await; 328 user_uuids.push(user_org.user_uuid.clone()); 329 } 330 } 331 } 332 }; 333 user_uuids 334 } 335 336 pub async fn save(&mut self, conn: &DbConn) -> EmptyResult { 337 self.update_users_revision(conn).await; 338 self.updated_at = Utc::now().naive_utc(); 339 db_run! { conn: 340 { 341 match diesel::replace_into(ciphers::table) 342 .values(CipherDb::to_db(self)) 343 .execute(conn) 344 { 345 Ok(_) => Ok(()), 346 // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first. 347 Err(result::Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _)) => { 348 diesel::update(ciphers::table) 349 .filter(ciphers::uuid.eq(&self.uuid)) 350 .set(CipherDb::to_db(self)) 351 .execute(conn) 352 .map_res("Error saving cipher") 353 } 354 Err(e) => Err(e.into()), 355 }.map_res("Error saving cipher") 356 } 357 } 358 } 359 360 pub async fn delete(&self, conn: &DbConn) -> EmptyResult { 361 self.update_users_revision(conn).await; 362 FolderCipher::delete_all_by_cipher(&self.uuid, conn).await?; 363 CollectionCipher::delete_all_by_cipher(&self.uuid, conn).await?; 364 Favorite::delete_all_by_cipher(&self.uuid, conn).await?; 365 db_run! { conn: { 366 diesel::delete(ciphers::table.filter(ciphers::uuid.eq(&self.uuid))) 367 .execute(conn) 368 .map_res("Error deleting cipher") 369 }} 370 } 371 372 pub async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult { 373 // TODO: Optimize this by executing a DELETE directly on the database, instead of first fetching. 374 for cipher in Self::find_by_org(org_uuid, conn).await { 375 cipher.delete(conn).await?; 376 } 377 Ok(()) 378 } 379 380 pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult { 381 for cipher in Self::find_owned_by_user(user_uuid, conn).await { 382 cipher.delete(conn).await?; 383 } 384 Ok(()) 385 } 386 387 pub async fn move_to_folder( 388 &self, 389 folder_uuid: Option<String>, 390 user_uuid: &str, 391 conn: &DbConn, 392 ) -> EmptyResult { 393 User::update_uuid_revision(user_uuid, conn).await; 394 match (self.get_folder_uuid(user_uuid, conn).await, folder_uuid) { 395 // No changes 396 (None, None) => Ok(()), 397 (Some(ref old), Some(ref new)) if old == new => Ok(()), 398 // Add to folder 399 (None, Some(new)) => FolderCipher::new(&new, &self.uuid).save(conn).await, 400 // Remove from folder 401 (Some(old), None) => { 402 match FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn).await { 403 Some(old) => old.delete(conn).await, 404 None => err!("Couldn't move from previous folder"), 405 } 406 } 407 // Move to another folder 408 (Some(old), Some(new)) => { 409 if let Some(old) = 410 FolderCipher::find_by_folder_and_cipher(&old, &self.uuid, conn).await 411 { 412 old.delete(conn).await?; 413 } 414 FolderCipher::new(&new, &self.uuid).save(conn).await 415 } 416 } 417 } 418 /// Returns whether this cipher is directly owned by the user. 419 fn is_owned_by_user(&self, user_uuid: &str) -> bool { 420 self.user_uuid.is_some() && self.user_uuid.as_ref().unwrap() == user_uuid 421 } 422 423 /// Returns whether this cipher is owned by an org in which the user has full access. 424 #[allow(clippy::else_if_without_else)] 425 async fn is_in_full_access_org( 426 &self, 427 user_uuid: &str, 428 cipher_sync_data: Option<&CipherSyncData>, 429 conn: &DbConn, 430 ) -> bool { 431 if let Some(ref org_uuid) = self.organization_uuid { 432 if let Some(cipher_sync_data) = cipher_sync_data { 433 if let Some(cached_user_org) = cipher_sync_data.user_organizations.get(org_uuid) { 434 return cached_user_org.has_full_access(); 435 } 436 } else if let Some(user_org) = 437 UserOrganization::find_by_user_and_org(user_uuid, org_uuid, conn).await 438 { 439 return user_org.has_full_access(); 440 } 441 } 442 false 443 } 444 /// Returns the user's access restrictions to this cipher. A return value 445 /// of None means that this cipher does not belong to the user, and is 446 /// not in any collection the user has access to. Otherwise, the user has 447 /// access to this cipher, and Some(read_only, hide_passwords) represents 448 /// the access restrictions. 449 async fn get_access_restrictions( 450 &self, 451 user_uuid: &str, 452 cipher_sync_data: Option<&CipherSyncData>, 453 conn: &DbConn, 454 ) -> Option<(bool, bool)> { 455 // Check whether this cipher is directly owned by the user, or is in 456 // a collection that the user has full access to. If so, there are no 457 // access restrictions. 458 if self.is_owned_by_user(user_uuid) 459 || self 460 .is_in_full_access_org(user_uuid, cipher_sync_data, conn) 461 .await 462 { 463 return Some((false, false)); 464 } 465 466 let rows = if let Some(cipher_sync_data) = cipher_sync_data { 467 let mut rows: Vec<(bool, bool)> = Vec::new(); 468 if let Some(collections) = cipher_sync_data.cipher_collections.get(&self.uuid) { 469 for collection in collections { 470 //User permissions 471 if let Some(uc) = cipher_sync_data.user_collections.get(collection) { 472 rows.push((uc.read_only, uc.hide_passwords)); 473 } 474 } 475 } 476 rows 477 } else { 478 self.get_user_collections_access_flags(user_uuid, conn) 479 .await 480 }; 481 482 if rows.is_empty() { 483 // This cipher isn't in any collections accessible to the user. 484 return None; 485 } 486 487 // A cipher can be in multiple collections with inconsistent access flags. 488 // For example, a cipher could be in one collection where the user has 489 // read-only access, but also in another collection where the user has 490 // read/write access. For a flag to be in effect for a cipher, upstream 491 // requires all collections the cipher is in to have that flag set. 492 // Therefore, we do a boolean AND of all values in each of the `read_only` 493 // and `hide_passwords` columns. This could ideally be done as part of the 494 // query, but Diesel doesn't support a min() or bool_and() function on 495 // booleans and this behavior isn't portable anyway. 496 let mut read_only = true; 497 let mut hide_passwords = true; 498 for tup in rows { 499 read_only &= tup.0; 500 hide_passwords &= tup.1; 501 } 502 Some((read_only, hide_passwords)) 503 } 504 505 async fn get_user_collections_access_flags( 506 &self, 507 user_uuid: &str, 508 conn: &DbConn, 509 ) -> Vec<(bool, bool)> { 510 db_run! {conn: { 511 // Check whether this cipher is in any collections accessible to the 512 // user. If so, retrieve the access flags for each collection. 513 ciphers::table 514 .filter(ciphers::uuid.eq(&self.uuid)) 515 .inner_join(ciphers_collections::table.on( 516 ciphers::uuid.eq(ciphers_collections::cipher_uuid))) 517 .inner_join(users_collections::table.on( 518 ciphers_collections::collection_uuid.eq(users_collections::collection_uuid) 519 .and(users_collections::user_uuid.eq(user_uuid)))) 520 .select((users_collections::read_only, users_collections::hide_passwords)) 521 .load::<(bool, bool)>(conn) 522 .expect("Error getting user access restrictions") 523 }} 524 } 525 526 pub async fn is_write_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool { 527 match self.get_access_restrictions(user_uuid, None, conn).await { 528 Some((read_only, _hide_passwords)) => !read_only, 529 None => false, 530 } 531 } 532 533 pub async fn is_accessible_to_user(&self, user_uuid: &str, conn: &DbConn) -> bool { 534 self.get_access_restrictions(user_uuid, None, conn) 535 .await 536 .is_some() 537 } 538 539 // Returns whether this cipher is a favorite of the specified user. 540 async fn is_favorite(&self, user_uuid: &str, conn: &DbConn) -> bool { 541 Favorite::is_favorite(&self.uuid, user_uuid, conn).await 542 } 543 544 // Sets whether this cipher is a favorite of the specified user. 545 pub async fn set_favorite( 546 &self, 547 favorite: Option<bool>, 548 user_uuid: &str, 549 conn: &DbConn, 550 ) -> EmptyResult { 551 match favorite { 552 None => Ok(()), // No change requested. 553 Some(status) => Favorite::set_favorite(status, &self.uuid, user_uuid, conn).await, 554 } 555 } 556 557 async fn get_folder_uuid(&self, user_uuid: &str, conn: &DbConn) -> Option<String> { 558 db_run! {conn: { 559 folders_ciphers::table 560 .inner_join(folders::table) 561 .filter(folders::user_uuid.eq(&user_uuid)) 562 .filter(folders_ciphers::cipher_uuid.eq(&self.uuid)) 563 .select(folders_ciphers::folder_uuid) 564 .first::<String>(conn) 565 .ok() 566 }} 567 } 568 569 pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> { 570 db_run! {conn: { 571 ciphers::table 572 .filter(ciphers::uuid.eq(uuid)) 573 .first::<CipherDb>(conn) 574 .ok() 575 .from_db() 576 }} 577 } 578 579 pub async fn find_by_uuid_and_org( 580 cipher_uuid: &str, 581 org_uuid: &str, 582 conn: &DbConn, 583 ) -> Option<Self> { 584 db_run! {conn: { 585 ciphers::table 586 .filter(ciphers::uuid.eq(cipher_uuid)) 587 .filter(ciphers::organization_uuid.eq(org_uuid)) 588 .first::<CipherDb>(conn) 589 .ok() 590 .from_db() 591 }} 592 } 593 594 // Find all ciphers accessible or visible to the specified user. 595 // 596 // "Accessible" means the user has read access to the cipher, either via 597 // direct ownership, collection or via group access. 598 // 599 // "Visible" usually means the same as accessible, except when an org 600 // owner/admin sets their account or group to have access to only selected 601 // collections in the org (presumably because they aren't interested in 602 // the other collections in the org). In this case, if `visible_only` is 603 // true, then the non-interesting ciphers will not be returned. As a 604 // result, those ciphers will not appear in "My Vault" for the org 605 // owner/admin, but they can still be accessed via the org vault view. 606 async fn find_by_user(user_uuid: &str, visible_only: bool, conn: &DbConn) -> Vec<Self> { 607 db_run! {conn: { 608 let mut query = ciphers::table 609 .left_join(ciphers_collections::table.on( 610 ciphers::uuid.eq(ciphers_collections::cipher_uuid) 611 )) 612 .left_join(users_organizations::table.on( 613 ciphers::organization_uuid.eq(users_organizations::org_uuid.nullable()) 614 .and(users_organizations::user_uuid.eq(user_uuid)) 615 .and(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))) 616 )) 617 .left_join(users_collections::table.on( 618 ciphers_collections::collection_uuid.eq(users_collections::collection_uuid) 619 // Ensure that users_collections::user_uuid is NULL for unconfirmed users. 620 .and(users_organizations::user_uuid.eq(users_collections::user_uuid)) 621 )) 622 .filter(ciphers::user_uuid.eq(user_uuid)) // Cipher owner 623 .or_filter(users_organizations::access_all.eq(true)) // access_all in org 624 .or_filter(users_collections::user_uuid.eq(user_uuid)) // Access to collection 625 .into_boxed(); 626 if !visible_only { 627 query = query.or_filter( 628 users_organizations::atype.le(i32::from(UserOrgType::Admin)) // Org admin/owner 629 ); 630 } 631 query 632 .select(ciphers::all_columns) 633 .distinct() 634 .load::<CipherDb>(conn).expect("Error loading ciphers").from_db() 635 }} 636 } 637 // Find all ciphers visible to the specified user. 638 pub async fn find_by_user_visible(user_uuid: &str, conn: &DbConn) -> Vec<Self> { 639 Self::find_by_user(user_uuid, true, conn).await 640 } 641 // Find all ciphers directly owned by the specified user. 642 pub async fn find_owned_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> { 643 db_run! {conn: { 644 ciphers::table 645 .filter( 646 ciphers::user_uuid.eq(user_uuid) 647 .and(ciphers::organization_uuid.is_null()) 648 ) 649 .load::<CipherDb>(conn).expect("Error loading ciphers").from_db() 650 }} 651 } 652 653 pub async fn count_owned_by_user(user_uuid: &str, conn: &DbConn) -> u64 { 654 u64::try_from({ 655 db_run! {conn: { 656 ciphers::table 657 .filter(ciphers::user_uuid.eq(user_uuid)) 658 .count() 659 .first::<i64>(conn) 660 .ok() 661 .unwrap_or(0) 662 }} 663 }) 664 .expect("underflow") 665 } 666 667 pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> { 668 db_run! {conn: { 669 ciphers::table 670 .filter(ciphers::organization_uuid.eq(org_uuid)) 671 .load::<CipherDb>(conn).expect("Error loading ciphers").from_db() 672 }} 673 } 674 675 pub async fn count_by_org(org_uuid: &str, conn: &DbConn) -> u64 { 676 u64::try_from({ 677 db_run! {conn: { 678 ciphers::table 679 .filter(ciphers::organization_uuid.eq(org_uuid)) 680 .count() 681 .first::<i64>(conn) 682 .ok() 683 .unwrap_or(0) 684 }} 685 }) 686 .expect("underflow") 687 } 688 689 pub async fn find_by_folder(folder_uuid: &str, conn: &DbConn) -> Vec<Self> { 690 db_run! {conn: { 691 folders_ciphers::table.inner_join(ciphers::table) 692 .filter(folders_ciphers::folder_uuid.eq(folder_uuid)) 693 .select(ciphers::all_columns) 694 .load::<CipherDb>(conn).expect("Error loading ciphers").from_db() 695 }} 696 } 697 698 /// Find all ciphers that were deleted before the specified datetime. 699 pub async fn find_deleted_before(dt: &NaiveDateTime, conn: &DbConn) -> Vec<Self> { 700 db_run! {conn: { 701 ciphers::table 702 .filter(ciphers::deleted_at.lt(dt)) 703 .load::<CipherDb>(conn).expect("Error loading ciphers").from_db() 704 }} 705 } 706 707 pub async fn get_collections(&self, user_id: String, conn: &DbConn) -> Vec<String> { 708 db_run! {conn: { 709 ciphers_collections::table 710 .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) 711 .inner_join(collections::table.on( 712 collections::uuid.eq(ciphers_collections::collection_uuid) 713 )) 714 .inner_join(users_organizations::table.on( 715 users_organizations::org_uuid.eq(collections::org_uuid) 716 .and(users_organizations::user_uuid.eq(user_id.clone())) 717 )) 718 .left_join(users_collections::table.on( 719 users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) 720 .and(users_collections::user_uuid.eq(user_id.clone())) 721 )) 722 .filter(users_organizations::access_all.eq(true) // User has access all 723 .or(users_collections::user_uuid.eq(user_id) // User has access to collection 724 .and(users_collections::read_only.eq(false))) 725 ) 726 .select(ciphers_collections::collection_uuid) 727 .load::<String>(conn).unwrap_or_default() 728 }} 729 } 730 731 pub async fn get_admin_collections(&self, user_id: String, conn: &DbConn) -> Vec<String> { 732 db_run! {conn: { 733 ciphers_collections::table 734 .filter(ciphers_collections::cipher_uuid.eq(&self.uuid)) 735 .inner_join(collections::table.on( 736 collections::uuid.eq(ciphers_collections::collection_uuid) 737 )) 738 .inner_join(users_organizations::table.on( 739 users_organizations::org_uuid.eq(collections::org_uuid) 740 .and(users_organizations::user_uuid.eq(user_id.clone())) 741 )) 742 .left_join(users_collections::table.on( 743 users_collections::collection_uuid.eq(ciphers_collections::collection_uuid) 744 .and(users_collections::user_uuid.eq(user_id.clone())) 745 )) 746 .filter(users_organizations::access_all.eq(true) // User has access all 747 .or(users_collections::user_uuid.eq(user_id) // User has access to collection 748 .and(users_collections::read_only.eq(false))) 749 .or(users_organizations::atype.le(i32::from(UserOrgType::Admin))) // User is admin or owner 750 ) 751 .select(ciphers_collections::collection_uuid) 752 .load::<String>(conn).unwrap_or_default() 753 }} 754 } 755 756 /// Return a Vec with (cipher_uuid, collection_uuid) 757 /// This is used during a full sync so we only need one query for all collections accessible. 758 pub async fn get_collections_with_cipher_by_user( 759 user_id: String, 760 conn: &DbConn, 761 ) -> Vec<(String, String)> { 762 db_run! {conn: { 763 ciphers_collections::table 764 .inner_join(collections::table.on( 765 collections::uuid.eq(ciphers_collections::collection_uuid) 766 )) 767 .inner_join(users_organizations::table.on( 768 users_organizations::org_uuid.eq(collections::org_uuid).and( 769 users_organizations::user_uuid.eq(user_id.clone()) 770 ) 771 )) 772 .left_join(users_collections::table.on( 773 users_collections::collection_uuid.eq(ciphers_collections::collection_uuid).and( 774 users_collections::user_uuid.eq(user_id.clone()) 775 ) 776 )) 777 .or_filter(users_collections::user_uuid.eq(user_id)) // User has access to collection 778 .or_filter(users_organizations::access_all.eq(true)) // User has access all 779 .or_filter(users_organizations::atype.le(i32::from(UserOrgType::Admin))) // User is admin or owner 780 .select(ciphers_collections::all_columns) 781 .distinct() 782 .load::<(String, String)>(conn).unwrap_or_default() 783 }} 784 } 785 }