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

organization.rs (28295B)


      1 use super::{CollectionUser, OrgPolicy, OrgPolicyType, User};
      2 use crate::db::models::{Collection, TwoFactorType};
      3 use crate::error::Error;
      4 use crate::util;
      5 use core::cmp::Ordering;
      6 use diesel::result::{self, DatabaseErrorKind};
      7 use serde_json::Value;
      8 use std::collections::HashMap;
      9 
     10 db_object! {
     11     #[derive(AsChangeset, Insertable, Queryable)]
     12     #[diesel(table_name = organizations)]
     13     pub struct Organization {
     14         pub uuid: String,
     15         pub name: String,
     16         pub billing_email: String,
     17         pub private_key: Option<String>,
     18         pub public_key: Option<String>,
     19     }
     20 
     21     #[derive(AsChangeset, Insertable, Queryable)]
     22     #[diesel(table_name = users_organizations)]
     23     pub struct UserOrganization {
     24         pub uuid: String,
     25         pub user_uuid: String,
     26         pub org_uuid: String,
     27         pub access_all: bool,
     28         pub akey: String,
     29         pub status: i32,
     30         pub atype: i32,
     31         pub reset_password_key: Option<String>,
     32         pub external_id: Option<String>,
     33     }
     34 }
     35 pub enum UserOrgStatus {
     36     Revoked = -1,
     37     Invited = 0,
     38     Accepted = 1,
     39     Confirmed = 2,
     40 }
     41 impl From<UserOrgStatus> for i32 {
     42     fn from(value: UserOrgStatus) -> Self {
     43         match value {
     44             UserOrgStatus::Revoked => -1i32,
     45             UserOrgStatus::Invited => 0i32,
     46             UserOrgStatus::Accepted => 1i32,
     47             UserOrgStatus::Confirmed => 2i32,
     48         }
     49     }
     50 }
     51 
     52 #[derive(Clone, Copy, Eq, PartialEq)]
     53 pub enum UserOrgType {
     54     Owner = 0,
     55     Admin = 1,
     56     User = 2,
     57     Manager = 3,
     58 }
     59 impl From<UserOrgType> for i32 {
     60     fn from(value: UserOrgType) -> Self {
     61         match value {
     62             UserOrgType::Owner => 0i32,
     63             UserOrgType::Admin => 1i32,
     64             UserOrgType::User => 2i32,
     65             UserOrgType::Manager => 3i32,
     66         }
     67     }
     68 }
     69 impl TryFrom<i32> for UserOrgType {
     70     type Error = Error;
     71     fn try_from(value: i32) -> Result<Self, Error> {
     72         match value {
     73             0i32 => Ok(Self::Owner),
     74             1i32 => Ok(Self::Admin),
     75             2i32 => Ok(Self::User),
     76             3i32 => Ok(Self::Manager),
     77             _ => {
     78                 const MSG: &str = "i32 is not valid UserOrgType";
     79                 Err(Error::new(MSG, MSG))
     80             }
     81         }
     82     }
     83 }
     84 impl From<UserOrgType> for usize {
     85     fn from(value: UserOrgType) -> Self {
     86         match value {
     87             UserOrgType::Owner => 0,
     88             UserOrgType::Admin => 1,
     89             UserOrgType::User => 2,
     90             UserOrgType::Manager => 3,
     91         }
     92     }
     93 }
     94 
     95 impl UserOrgType {
     96     pub fn from_str(s: &str) -> Option<Self> {
     97         match s {
     98             "0" | "Owner" => Some(Self::Owner),
     99             "1" | "Admin" => Some(Self::Admin),
    100             "2" | "User" => Some(Self::User),
    101             "3" | "Manager" => Some(Self::Manager),
    102             _ => None,
    103         }
    104     }
    105 }
    106 
    107 impl Ord for UserOrgType {
    108     fn cmp(&self, other: &Self) -> Ordering {
    109         match *self {
    110             Self::Owner => match *other {
    111                 Self::Owner => Ordering::Equal,
    112                 Self::Admin | Self::User | Self::Manager => Ordering::Greater,
    113             },
    114             Self::Admin => match *other {
    115                 Self::Owner => Ordering::Less,
    116                 Self::Admin => Ordering::Equal,
    117                 Self::User | Self::Manager => Ordering::Greater,
    118             },
    119             Self::User => match *other {
    120                 Self::User => Ordering::Equal,
    121                 Self::Owner | Self::Admin | Self::Manager => Ordering::Less,
    122             },
    123             Self::Manager => match *other {
    124                 Self::Owner | Self::Admin => Ordering::Less,
    125                 Self::User => Ordering::Greater,
    126                 Self::Manager => Ordering::Equal,
    127             },
    128         }
    129     }
    130 }
    131 
    132 impl PartialOrd for UserOrgType {
    133     fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
    134         Some(self.cmp(other))
    135     }
    136 }
    137 
    138 impl PartialEq<i32> for UserOrgType {
    139     fn eq(&self, other: &i32) -> bool {
    140         *other == i32::from(*self)
    141     }
    142 }
    143 
    144 impl PartialOrd<i32> for UserOrgType {
    145     fn partial_cmp(&self, other: &i32) -> Option<Ordering> {
    146         if let Ok(other) = Self::try_from(*other) {
    147             return Some(self.cmp(&other));
    148         }
    149         None
    150     }
    151 
    152     fn gt(&self, other: &i32) -> bool {
    153         matches!(self.partial_cmp(other), Some(Ordering::Greater))
    154     }
    155 
    156     fn ge(&self, other: &i32) -> bool {
    157         matches!(
    158             self.partial_cmp(other),
    159             Some(Ordering::Greater | Ordering::Equal)
    160         )
    161     }
    162 }
    163 
    164 impl PartialEq<UserOrgType> for i32 {
    165     fn eq(&self, other: &UserOrgType) -> bool {
    166         *self == Self::from(*other)
    167     }
    168 }
    169 
    170 impl PartialOrd<UserOrgType> for i32 {
    171     fn partial_cmp(&self, other: &UserOrgType) -> Option<Ordering> {
    172         if let Ok(self_type) = UserOrgType::try_from(*self) {
    173             return Some(self_type.cmp(other));
    174         }
    175         None
    176     }
    177 
    178     fn lt(&self, other: &UserOrgType) -> bool {
    179         matches!(self.partial_cmp(other), Some(Ordering::Less) | None)
    180     }
    181 
    182     fn le(&self, other: &UserOrgType) -> bool {
    183         matches!(
    184             self.partial_cmp(other),
    185             Some(Ordering::Less | Ordering::Equal) | None
    186         )
    187     }
    188 }
    189 
    190 /// Local methods
    191 impl Organization {
    192     // https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/Organizations/OrganizationResponseModel.cs
    193     pub fn to_json(&self) -> Value {
    194         json!({
    195             "id": self.uuid,
    196             "identifier": null, // not supported by us
    197             "name": self.name,
    198             "seats": null,
    199             "maxAutoscaleSeats": null,
    200             "maxCollections": null,
    201             "maxStorageGb": i16::MAX,
    202             "use2fa": true,
    203             "useCustomPermissions": false,
    204             "useDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
    205             "useEvents": false,
    206             "useGroups": false,
    207             "useTotp": true,
    208             "usePolicies": true,
    209             "useSso": false, // Not supported
    210             "selfHost": true,
    211             "useApi": true,
    212             "hasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
    213             "useResetPassword": false,
    214             "businessName": null,
    215             "businessAddress1": null,
    216             "businessAddress2": null,
    217             "businessAddress3": null,
    218             "businessCountry": null,
    219             "businessTaxNumber": null,
    220             "billingEmail": self.billing_email,
    221             "planType": 6,
    222             "usersGetPremium": true,
    223             "object": "organization",
    224         })
    225     }
    226 }
    227 // Used to either subtract or add to the current status
    228 // The number 128 should be fine, it is well within the range of an i32
    229 // The same goes for the database where we only use INTEGER (the same as an i32)
    230 // It should also provide enough room for 100+ types, which i doubt will ever happen.
    231 static ACTIVATE_REVOKE_DIFF: i32 = 128i32;
    232 
    233 impl UserOrganization {
    234     pub fn new(user_uuid: String, org_uuid: String) -> Self {
    235         Self {
    236             uuid: util::get_uuid(),
    237             user_uuid,
    238             org_uuid,
    239             access_all: false,
    240             akey: String::new(),
    241             status: i32::from(UserOrgStatus::Accepted),
    242             atype: i32::from(UserOrgType::User),
    243             reset_password_key: None,
    244             external_id: None,
    245         }
    246     }
    247 
    248     pub fn restore(&mut self) -> bool {
    249         if self.status < i32::from(UserOrgStatus::Invited) {
    250             self.status = self
    251                 .status
    252                 .checked_add(ACTIVATE_REVOKE_DIFF)
    253                 .expect("i32 add overflowed");
    254             true
    255         } else {
    256             false
    257         }
    258     }
    259 
    260     pub fn revoke(&mut self) -> bool {
    261         if self.status > i32::from(UserOrgStatus::Revoked) {
    262             self.status = self
    263                 .status
    264                 .checked_sub(ACTIVATE_REVOKE_DIFF)
    265                 .expect("i32 sub underflowed");
    266             true
    267         } else {
    268             false
    269         }
    270     }
    271 
    272     pub fn set_external_id(&mut self, external_id: Option<String>) -> bool {
    273         //Check if external id is empty. We don't want to have
    274         //empty strings in the database
    275         if self.external_id != external_id {
    276             self.external_id = match external_id {
    277                 Some(external_id) if !external_id.is_empty() => Some(external_id),
    278                 _ => None,
    279             };
    280             return true;
    281         }
    282         false
    283     }
    284 }
    285 
    286 use crate::api::EmptyResult;
    287 use crate::db::DbConn;
    288 use crate::error::MapResult;
    289 
    290 /// Database methods
    291 impl Organization {
    292     pub async fn save(&self, conn: &DbConn) -> EmptyResult {
    293         for user_org in &UserOrganization::find_by_org(&self.uuid, conn).await {
    294             User::update_uuid_revision(&user_org.user_uuid, conn).await;
    295         }
    296         db_run! { conn:
    297              {
    298                 match diesel::replace_into(organizations::table)
    299                     .values(OrganizationDb::to_db(self))
    300                     .execute(conn)
    301                 {
    302                     Ok(_) => Ok(()),
    303                     // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
    304                     Err(result::Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _)) => {
    305                         diesel::update(organizations::table)
    306                             .filter(organizations::uuid.eq(&self.uuid))
    307                             .set(OrganizationDb::to_db(self))
    308                             .execute(conn)
    309                             .map_res("Error saving organization")
    310                     }
    311                     Err(e) => Err(e.into()),
    312                 }.map_res("Error saving organization")
    313             }
    314         }
    315     }
    316 
    317     pub async fn delete(self, conn: &DbConn) -> EmptyResult {
    318         use super::{Cipher, Collection};
    319         Cipher::delete_all_by_organization(&self.uuid, conn).await?;
    320         Collection::delete_all_by_organization(&self.uuid, conn).await?;
    321         UserOrganization::delete_all_by_organization(&self.uuid, conn).await?;
    322         OrgPolicy::delete_all_by_organization(&self.uuid, conn).await?;
    323         db_run! { conn: {
    324             diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
    325                 .execute(conn)
    326                 .map_res("Error saving organization")
    327         }}
    328     }
    329 
    330     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
    331         db_run! { conn: {
    332             organizations::table
    333                 .filter(organizations::uuid.eq(uuid))
    334                 .first::<OrganizationDb>(conn)
    335                 .ok().from_db()
    336         }}
    337     }
    338 }
    339 
    340 impl UserOrganization {
    341     pub async fn to_json(&self, conn: &DbConn) -> Value {
    342         let org = Organization::find_by_uuid(&self.org_uuid, conn)
    343             .await
    344             .unwrap();
    345         let permissions = json!({
    346                 // TODO: Add support for Custom User Roles
    347                 // See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
    348                 "accessEventLogs": false,
    349                 "accessImportExport": false,
    350                 "accessReports": false,
    351                 "createNewCollections": false,
    352                 "editAnyCollection": false,
    353                 "deleteAnyCollection": false,
    354                 "editAssignedCollections": false,
    355                 "deleteAssignedCollections": false,
    356                 "manageGroups": false,
    357                 "managePolicies": false,
    358                 "manageSso": false, // Not supported
    359                 "manageUsers": false,
    360                 "manageResetPassword": false,
    361                 "manageScim": false // Not supported (Not AGPLv3 Licensed)
    362         });
    363         // https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/ProfileOrganizationResponseModel.cs
    364         json!({
    365             "id": self.org_uuid,
    366             "identifier": null, // Not supported
    367             "name": org.name,
    368             "seats": null,
    369             "maxAutoscaleSeats": null,
    370             "maxCollections": null,
    371             "usersGetPremium": true,
    372             "use2fa": true,
    373             "useDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
    374             "useEvents": false,
    375             "useGroups": false,
    376             "useTotp": true,
    377             "useScim": false, // Not supported (Not AGPLv3 Licensed)
    378             "usePolicies": true,
    379             "useApi": false,
    380             "selfHost": true,
    381             "hasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
    382             "resetPasswordEnrolled": self.reset_password_key.is_some(),
    383             "useResetPassword": false,
    384             "ssoBound": false, // Not supported
    385             "useSso": false, // Not supported
    386             "useKeyConnector": false,
    387             "useSecretsManager": false,
    388             "usePasswordManager": true,
    389             "useCustomPermissions": false,
    390             "useActivateAutofillPolicy": false,
    391             "organizationUserId": self.uuid,
    392             "providerId": null,
    393             "providerName": null,
    394             "providerType": null,
    395             "familySponsorshipFriendlyName": null,
    396             "familySponsorshipAvailable": false,
    397             "planProductType": 3i32,
    398             "productTierType": 3i32,
    399             "keyConnectorEnabled": false,
    400             "keyConnectorUrl": null,
    401             "familySponsorshipLastSyncDate": null,
    402             "familySponsorshipValidUntil": null,
    403             "familySponsorshipToDelete": null,
    404             "accessSecretsManager": false,
    405             "limitCollectionCreationDeletion": true,
    406             "allowAdminAccessToAllCollectionItems": true,
    407             "flexibleCollections": false,
    408             "permissions": permissions,
    409             "maxStorageGb": i16::MAX,
    410             "userId": self.user_uuid,
    411             "key": self.akey,
    412             "status": self.status,
    413             "type": self.atype,
    414             "enabled": true,
    415             "object": "profileOrganization",
    416         })
    417     }
    418     pub async fn to_json_user_details(&self, include_collections: bool, conn: &DbConn) -> Value {
    419         let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
    420         // Because BitWarden want the status to be -1 for revoked users we need to catch that here.
    421         // We subtract/add a number so we can restore/activate the user to it's previous state again.
    422         let status = if self.status < i32::from(UserOrgStatus::Revoked) {
    423             i32::from(UserOrgStatus::Revoked)
    424         } else {
    425             self.status
    426         };
    427 
    428         let twofactor_enabled = TwoFactorType::has_twofactor(&user.uuid, conn)
    429             .await
    430             .expect("unable to get two factor information");
    431         let groups: Vec<String> = Vec::new();
    432         let collections: Vec<Value> = if include_collections {
    433             // Get all collections for the user here already to prevent more queries
    434             let cu: HashMap<String, CollectionUser> =
    435                 CollectionUser::find_by_organization_and_user_uuid(
    436                     &self.org_uuid,
    437                     &self.user_uuid,
    438                     conn,
    439                 )
    440                 .await
    441                 .into_iter()
    442                 .map(|cu| (cu.collection_uuid.clone(), cu))
    443                 .collect();
    444             Collection::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
    445                 .await
    446                 .into_iter()
    447                 .map(|c| {
    448                     let (read_only, hide_passwords, can_manage) = if self.has_full_access() {
    449                         (false, false, self.atype == UserOrgType::Manager)
    450                     } else if let Some(cu) = cu.get(&c.uuid) {
    451                         (
    452                             cu.read_only,
    453                             cu.hide_passwords,
    454                             self.atype == UserOrgType::Manager
    455                                 && !cu.read_only
    456                                 && !cu.hide_passwords,
    457                         )
    458                     } else {
    459                         (true, true, false)
    460                     };
    461 
    462                     json!({
    463                         "id": c.uuid,
    464                         "readOnly": read_only,
    465                         "hidePasswords": hide_passwords,
    466                         "manage": can_manage,
    467                     })
    468                 })
    469                 .collect()
    470         } else {
    471             Vec::with_capacity(0)
    472         };
    473 
    474         json!({
    475             "id": self.uuid,
    476             "userId": self.user_uuid,
    477             "name": user.name,
    478             "email": user.email,
    479             "externalId": self.external_id,
    480             "avatarColor": user.avatar_color,
    481             "groups": groups,
    482             "collections": collections,
    483             "status": status,
    484             "type": self.atype,
    485             "accessAll": self.access_all,
    486             "twoFactorEnabled": twofactor_enabled,
    487             "resetPasswordEnrolled": self.reset_password_key.is_some(),
    488             "object": "organizationUserUserDetails",
    489         })
    490     }
    491 
    492     pub fn to_json_user_access_restrictions(&self, col_user: &CollectionUser) -> Value {
    493         json!({
    494             "id": self.uuid,
    495             "readOnly": col_user.read_only,
    496             "hidePasswords": col_user.hide_passwords,
    497         })
    498     }
    499 
    500     pub async fn save(&self, conn: &DbConn) -> EmptyResult {
    501         User::update_uuid_revision(&self.user_uuid, conn).await;
    502         db_run! { conn:
    503              {
    504                 match diesel::replace_into(users_organizations::table)
    505                     .values(UserOrganizationDb::to_db(self))
    506                     .execute(conn)
    507                 {
    508                     Ok(_) => Ok(()),
    509                     // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
    510                     Err(result::Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _)) => {
    511                         diesel::update(users_organizations::table)
    512                             .filter(users_organizations::uuid.eq(&self.uuid))
    513                             .set(UserOrganizationDb::to_db(self))
    514                             .execute(conn)
    515                             .map_res("Error adding user to organization")
    516                     },
    517                     Err(e) => Err(e.into()),
    518                 }.map_res("Error adding user to organization")
    519             }
    520         }
    521     }
    522 
    523     pub async fn delete(self, conn: &DbConn) -> EmptyResult {
    524         User::update_uuid_revision(&self.user_uuid, conn).await;
    525         CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?;
    526         db_run! { conn: {
    527             diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
    528                 .execute(conn)
    529                 .map_res("Error removing user from organization")
    530         }}
    531     }
    532 
    533     async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
    534         for user_org in Self::find_by_org(org_uuid, conn).await {
    535             user_org.delete(conn).await?;
    536         }
    537         Ok(())
    538     }
    539 
    540     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
    541         for user_org in Self::find_any_state_by_user(user_uuid, conn).await {
    542             user_org.delete(conn).await?;
    543         }
    544         Ok(())
    545     }
    546 
    547     pub async fn find_by_email_and_org(email: &str, org_id: &str, conn: &DbConn) -> Option<Self> {
    548         if let Some(user) = super::User::find_by_mail(email, conn).await {
    549             if let Some(user_org) = Self::find_by_user_and_org(&user.uuid, org_id, conn).await {
    550                 return Some(user_org);
    551             }
    552         }
    553 
    554         None
    555     }
    556 
    557     pub fn has_status(&self, status: UserOrgStatus) -> bool {
    558         self.status == i32::from(status)
    559     }
    560 
    561     pub fn has_full_access(&self) -> bool {
    562         (self.access_all || self.atype >= UserOrgType::Admin)
    563             && self.has_status(UserOrgStatus::Confirmed)
    564     }
    565 
    566     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
    567         db_run! { conn: {
    568             users_organizations::table
    569                 .filter(users_organizations::uuid.eq(uuid))
    570                 .first::<UserOrganizationDb>(conn)
    571                 .ok().from_db()
    572         }}
    573     }
    574 
    575     pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
    576         db_run! { conn: {
    577             users_organizations::table
    578                 .filter(users_organizations::uuid.eq(uuid))
    579                 .filter(users_organizations::org_uuid.eq(org_uuid))
    580                 .first::<UserOrganizationDb>(conn)
    581                 .ok().from_db()
    582         }}
    583     }
    584 
    585     pub async fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
    586         db_run! { conn: {
    587             users_organizations::table
    588                 .filter(users_organizations::user_uuid.eq(user_uuid))
    589                 .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
    590                 .load::<UserOrganizationDb>(conn)
    591                 .unwrap_or_default().from_db()
    592         }}
    593     }
    594 
    595     pub async fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
    596         db_run! { conn: {
    597             users_organizations::table
    598                 .filter(users_organizations::user_uuid.eq(user_uuid))
    599                 .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Invited)))
    600                 .load::<UserOrganizationDb>(conn)
    601                 .unwrap_or_default().from_db()
    602         }}
    603     }
    604 
    605     async fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
    606         db_run! { conn: {
    607             users_organizations::table
    608                 .filter(users_organizations::user_uuid.eq(user_uuid))
    609                 .load::<UserOrganizationDb>(conn)
    610                 .unwrap_or_default().from_db()
    611         }}
    612     }
    613 
    614     pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> u64 {
    615         u64::try_from({
    616             db_run! { conn: {
    617                 users_organizations::table
    618                     .filter(users_organizations::user_uuid.eq(user_uuid))
    619                     .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Accepted))
    620                     .or(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))))
    621                     .count()
    622                     .first::<i64>(conn)
    623                     .unwrap_or(0)
    624             }}
    625         })
    626         .expect("underflow")
    627     }
    628 
    629     pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
    630         db_run! { conn: {
    631             users_organizations::table
    632                 .filter(users_organizations::org_uuid.eq(org_uuid))
    633                 .load::<UserOrganizationDb>(conn)
    634                 .expect("Error loading user organizations").from_db()
    635         }}
    636     }
    637 
    638     pub async fn find_by_org_and_type(
    639         org_uuid: &str,
    640         atype: UserOrgType,
    641         conn: &DbConn,
    642     ) -> Vec<Self> {
    643         db_run! { conn: {
    644             users_organizations::table
    645                 .filter(users_organizations::org_uuid.eq(org_uuid))
    646                 .filter(users_organizations::atype.eq(i32::from(atype)))
    647                 .load::<UserOrganizationDb>(conn)
    648                 .expect("Error loading user organizations").from_db()
    649         }}
    650     }
    651 
    652     pub async fn count_confirmed_by_org_and_type(
    653         org_uuid: &str,
    654         atype: UserOrgType,
    655         conn: &DbConn,
    656     ) -> u64 {
    657         u64::try_from({
    658             db_run! { conn: {
    659                 users_organizations::table
    660                     .filter(users_organizations::org_uuid.eq(org_uuid))
    661                     .filter(users_organizations::atype.eq(i32::from(atype)))
    662                     .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
    663                     .count()
    664                     .first::<i64>(conn)
    665                     .unwrap_or(0)
    666             }}
    667         })
    668         .expect("underflow")
    669     }
    670 
    671     pub async fn find_by_user_and_org(
    672         user_uuid: &str,
    673         org_uuid: &str,
    674         conn: &DbConn,
    675     ) -> Option<Self> {
    676         db_run! { conn: {
    677             users_organizations::table
    678                 .filter(users_organizations::user_uuid.eq(user_uuid))
    679                 .filter(users_organizations::org_uuid.eq(org_uuid))
    680                 .first::<UserOrganizationDb>(conn)
    681                 .ok().from_db()
    682         }}
    683     }
    684 
    685     pub async fn find_confirmed_by_user_and_org(
    686         user_uuid: &str,
    687         org_uuid: &str,
    688         conn: &DbConn,
    689     ) -> Option<Self> {
    690         db_run! { conn: {
    691             users_organizations::table
    692                 .filter(users_organizations::user_uuid.eq(user_uuid))
    693                 .filter(users_organizations::org_uuid.eq(org_uuid))
    694                 .filter(
    695                     users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
    696                 )
    697                 .first::<UserOrganizationDb>(conn)
    698                 .ok().from_db()
    699         }}
    700     }
    701 
    702     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
    703         db_run! { conn: {
    704             users_organizations::table
    705                 .filter(users_organizations::user_uuid.eq(user_uuid))
    706                 .load::<UserOrganizationDb>(conn)
    707                 .expect("Error loading user organizations").from_db()
    708         }}
    709     }
    710 
    711     pub async fn find_by_user_and_policy(
    712         user_uuid: &str,
    713         policy_type: OrgPolicyType,
    714         conn: &DbConn,
    715     ) -> Vec<Self> {
    716         db_run! { conn: {
    717             users_organizations::table
    718                 .inner_join(
    719                     org_policies::table.on(
    720                         org_policies::org_uuid.eq(users_organizations::org_uuid)
    721                             .and(users_organizations::user_uuid.eq(user_uuid))
    722                             .and(org_policies::atype.eq(i32::from(policy_type)))
    723                             .and(org_policies::enabled.eq(true)))
    724                 )
    725                 .filter(
    726                     users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
    727                 )
    728                 .select(users_organizations::all_columns)
    729                 .load::<UserOrganizationDb>(conn)
    730                 .unwrap_or_default().from_db()
    731         }}
    732     }
    733 
    734     pub async fn find_by_cipher_and_org(
    735         cipher_uuid: &str,
    736         org_uuid: &str,
    737         conn: &DbConn,
    738     ) -> Vec<Self> {
    739         db_run! { conn: {
    740             users_organizations::table
    741             .filter(users_organizations::org_uuid.eq(org_uuid))
    742             .left_join(users_collections::table.on(
    743                 users_collections::user_uuid.eq(users_organizations::user_uuid)
    744             ))
    745             .left_join(ciphers_collections::table.on(
    746                 ciphers_collections::collection_uuid.eq(users_collections::collection_uuid).and(
    747                     ciphers_collections::cipher_uuid.eq(&cipher_uuid)
    748                 )
    749             ))
    750             .filter(
    751                 users_organizations::access_all.eq(true).or( // AccessAll..
    752                     ciphers_collections::cipher_uuid.eq(&cipher_uuid) // ..or access to collection with cipher
    753                 )
    754             )
    755             .select(users_organizations::all_columns)
    756             .distinct()
    757             .load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db()
    758         }}
    759     }
    760 
    761     pub async fn find_by_collection_and_org(
    762         collection_uuid: &str,
    763         org_uuid: &str,
    764         conn: &DbConn,
    765     ) -> Vec<Self> {
    766         db_run! { conn: {
    767             users_organizations::table
    768             .filter(users_organizations::org_uuid.eq(org_uuid))
    769             .left_join(users_collections::table.on(
    770                 users_collections::user_uuid.eq(users_organizations::user_uuid)
    771             ))
    772             .filter(
    773                 users_organizations::access_all.eq(true).or( // AccessAll..
    774                     users_collections::collection_uuid.eq(&collection_uuid) // ..or access to collection with cipher
    775                 )
    776             )
    777             .select(users_organizations::all_columns)
    778             .load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db()
    779         }}
    780     }
    781 }