vw_small

Hardened fork of Vaultwarden (https://github.com/dani-garcia/vaultwarden) with fewer features.
git clone https://git.philomathiclife.com/repos/vw_small
Log | Files | Refs | README

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 }