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

collection.rs (23811B)


      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 false, but they can manage all collections anyway
     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 }