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 (29835B)


      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             "maxCollections": null,
    200             "maxStorageGb": i16::MAX,
    201             "use2fa": true,
    202             "useCustomPermissions": false,
    203             "useDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
    204             "useEvents": false,
    205             "useGroups": false,
    206             "useTotp": true,
    207             "usePolicies": true,
    208             "useSso": false, // Not supported
    209             "selfHost": true,
    210             "useApi": true,
    211             "hasPublicAndPrivateKeys": self.private_key.is_some() && self.public_key.is_some(),
    212             "useResetPassword": false,
    213             "businessName": null,
    214             "businessAddress1": null,
    215             "businessAddress2": null,
    216             "businessAddress3": null,
    217             "businessCountry": null,
    218             "businessTaxNumber": null,
    219             "billingEmail": self.billing_email,
    220             "planType": 6,
    221             "usersGetPremium": true,
    222             "object": "organization",
    223         })
    224     }
    225 }
    226 // Used to either subtract or add to the current status
    227 // The number 128 should be fine, it is well within the range of an i32
    228 // The same goes for the database where we only use INTEGER (the same as an i32)
    229 // It should also provide enough room for 100+ types, which i doubt will ever happen.
    230 static ACTIVATE_REVOKE_DIFF: i32 = 128i32;
    231 
    232 impl UserOrganization {
    233     pub fn new(user_uuid: String, org_uuid: String) -> Self {
    234         Self {
    235             uuid: util::get_uuid(),
    236             user_uuid,
    237             org_uuid,
    238             access_all: false,
    239             akey: String::new(),
    240             status: i32::from(UserOrgStatus::Accepted),
    241             atype: i32::from(UserOrgType::User),
    242             reset_password_key: None,
    243             external_id: None,
    244         }
    245     }
    246 
    247     pub fn restore(&mut self) -> bool {
    248         if self.status < i32::from(UserOrgStatus::Invited) {
    249             self.status = self
    250                 .status
    251                 .checked_add(ACTIVATE_REVOKE_DIFF)
    252                 .expect("i32 add overflowed");
    253             true
    254         } else {
    255             false
    256         }
    257     }
    258 
    259     pub fn revoke(&mut self) -> bool {
    260         if self.status > i32::from(UserOrgStatus::Revoked) {
    261             self.status = self
    262                 .status
    263                 .checked_sub(ACTIVATE_REVOKE_DIFF)
    264                 .expect("i32 sub underflowed");
    265             true
    266         } else {
    267             false
    268         }
    269     }
    270 
    271     /// Return the status of the user in an unrevoked state
    272     pub fn get_unrevoked_status(&self) -> i32 {
    273         if self.status <= i32::from(UserOrgStatus::Revoked) {
    274             return self
    275                 .status
    276                 .checked_add(ACTIVATE_REVOKE_DIFF)
    277                 .unwrap_or_else(|| panic!("overflow"));
    278         }
    279         self.status
    280     }
    281 
    282     pub fn set_external_id(&mut self, external_id: Option<String>) -> bool {
    283         //Check if external id is empty. We don't want to have
    284         //empty strings in the database
    285         if self.external_id != external_id {
    286             self.external_id = match external_id {
    287                 Some(external_id) if !external_id.is_empty() => Some(external_id),
    288                 _ => None,
    289             };
    290             return true;
    291         }
    292         false
    293     }
    294 }
    295 
    296 use crate::api::EmptyResult;
    297 use crate::db::DbConn;
    298 use crate::error::MapResult;
    299 
    300 /// Database methods
    301 impl Organization {
    302     pub async fn save(&self, conn: &DbConn) -> EmptyResult {
    303         for user_org in &UserOrganization::find_by_org(&self.uuid, conn).await {
    304             User::update_uuid_revision(&user_org.user_uuid, conn).await;
    305         }
    306         db_run! { conn:
    307              {
    308                 match diesel::replace_into(organizations::table)
    309                     .values(OrganizationDb::to_db(self))
    310                     .execute(conn)
    311                 {
    312                     Ok(_) => Ok(()),
    313                     // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
    314                     Err(result::Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _)) => {
    315                         diesel::update(organizations::table)
    316                             .filter(organizations::uuid.eq(&self.uuid))
    317                             .set(OrganizationDb::to_db(self))
    318                             .execute(conn)
    319                             .map_res("Error saving organization")
    320                     }
    321                     Err(e) => Err(e.into()),
    322                 }.map_res("Error saving organization")
    323             }
    324         }
    325     }
    326 
    327     pub async fn delete(self, conn: &DbConn) -> EmptyResult {
    328         use super::{Cipher, Collection};
    329         Cipher::delete_all_by_organization(&self.uuid, conn).await?;
    330         Collection::delete_all_by_organization(&self.uuid, conn).await?;
    331         UserOrganization::delete_all_by_organization(&self.uuid, conn).await?;
    332         OrgPolicy::delete_all_by_organization(&self.uuid, conn).await?;
    333         db_run! { conn: {
    334             diesel::delete(organizations::table.filter(organizations::uuid.eq(self.uuid)))
    335                 .execute(conn)
    336                 .map_res("Error saving organization")
    337         }}
    338     }
    339 
    340     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
    341         db_run! { conn: {
    342             organizations::table
    343                 .filter(organizations::uuid.eq(uuid))
    344                 .first::<OrganizationDb>(conn)
    345                 .ok().from_db()
    346         }}
    347     }
    348 }
    349 
    350 impl UserOrganization {
    351     pub async fn to_json(&self, conn: &DbConn) -> Value {
    352         let org = Organization::find_by_uuid(&self.org_uuid, conn)
    353             .await
    354             .unwrap();
    355         let permissions = json!({
    356                 // TODO: Add support for Custom User Roles
    357                 // See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
    358                 "accessEventLogs": false,
    359                 "accessImportExport": false,
    360                 "accessReports": false,
    361                 "createNewCollections": false,
    362                 "editAnyCollection": false,
    363                 "deleteAnyCollection": false,
    364                 "editAssignedCollections": false,
    365                 "deleteAssignedCollections": false,
    366                 "manageGroups": false,
    367                 "managePolicies": false,
    368                 "manageSso": false, // Not supported
    369                 "manageUsers": false,
    370                 "manageResetPassword": false,
    371                 "manageScim": false // Not supported (Not AGPLv3 Licensed)
    372         });
    373         // https://github.com/bitwarden/server/blob/13d1e74d6960cf0d042620b72d85bf583a4236f7/src/Api/Models/Response/ProfileOrganizationResponseModel.cs
    374         json!({
    375             "id": self.org_uuid,
    376             "identifier": null, // Not supported
    377             "name": org.name,
    378             "seats": null,
    379             "maxCollections": null,
    380             "usersGetPremium": true,
    381             "use2fa": true,
    382             "useDirectory": false, // Is supported, but this value isn't checked anywhere (yet)
    383             "useEvents": false,
    384             "useGroups": false,
    385             "useTotp": true,
    386             "useScim": false, // Not supported (Not AGPLv3 Licensed)
    387             "usePolicies": true,
    388             "useApi": false,
    389             "selfHost": true,
    390             "hasPublicAndPrivateKeys": org.private_key.is_some() && org.public_key.is_some(),
    391             "resetPasswordEnrolled": self.reset_password_key.is_some(),
    392             "useResetPassword": false,
    393             "ssoBound": false, // Not supported
    394             "useSso": false, // Not supported
    395             "useKeyConnector": false,
    396             "useSecretsManager": false,
    397             "usePasswordManager": true,
    398             "useCustomPermissions": false,
    399             "useActivateAutofillPolicy": false,
    400             "organizationUserId": self.uuid,
    401             "providerId": null,
    402             "providerName": null,
    403             "providerType": null,
    404             "familySponsorshipFriendlyName": null,
    405             "familySponsorshipAvailable": false,
    406             "planProductType": 3i32,
    407             "productTierType": 3i32,
    408             "keyConnectorEnabled": false,
    409             "keyConnectorUrl": null,
    410             "familySponsorshipLastSyncDate": null,
    411             "familySponsorshipValidUntil": null,
    412             "familySponsorshipToDelete": null,
    413             "accessSecretsManager": false,
    414             "limitCollectionCreationDeletion": false, // This should be set to true only when we can handle roles like createNewCollections
    415             "allowAdminAccessToAllCollectionItems": true,
    416             "flexibleCollections": false,
    417             "permissions": permissions,
    418             "maxStorageGb": i16::MAX,
    419             "userId": self.user_uuid,
    420             "key": self.akey,
    421             "status": self.status,
    422             "type": self.atype,
    423             "enabled": true,
    424             "object": "profileOrganization",
    425         })
    426     }
    427     pub async fn to_json_user_details(&self, include_collections: bool, conn: &DbConn) -> Value {
    428         let user = User::find_by_uuid(&self.user_uuid, conn).await.unwrap();
    429         // Because BitWarden want the status to be -1 for revoked users we need to catch that here.
    430         // We subtract/add a number so we can restore/activate the user to it's previous state again.
    431         let status = if self.status < i32::from(UserOrgStatus::Revoked) {
    432             i32::from(UserOrgStatus::Revoked)
    433         } else {
    434             self.status
    435         };
    436 
    437         let twofactor_enabled = TwoFactorType::has_twofactor(&user.uuid, conn)
    438             .await
    439             .expect("unable to get two factor information");
    440         let groups: Vec<String> = Vec::new();
    441         let collections: Vec<Value> = if include_collections {
    442             // Get all collections for the user here already to prevent more queries
    443             let cu: HashMap<String, CollectionUser> =
    444                 CollectionUser::find_by_organization_and_user_uuid(
    445                     &self.org_uuid,
    446                     &self.user_uuid,
    447                     conn,
    448                 )
    449                 .await
    450                 .into_iter()
    451                 .map(|cu| (cu.collection_uuid.clone(), cu))
    452                 .collect();
    453             Collection::find_by_organization_and_user_uuid(&self.org_uuid, &self.user_uuid, conn)
    454                 .await
    455                 .into_iter()
    456                 .map(|c| {
    457                     let (read_only, hide_passwords, can_manage) = if self.has_full_access() {
    458                         (false, false, self.atype >= UserOrgType::Manager)
    459                     } else if let Some(cu) = cu.get(&c.uuid) {
    460                         (
    461                             cu.read_only,
    462                             cu.hide_passwords,
    463                             self.atype == UserOrgType::Manager
    464                                 && !cu.read_only
    465                                 && !cu.hide_passwords,
    466                         )
    467                     } else {
    468                         (true, true, false)
    469                     };
    470 
    471                     json!({
    472                         "id": c.uuid,
    473                         "readOnly": read_only,
    474                         "hidePasswords": hide_passwords,
    475                         "manage": can_manage,
    476                     })
    477                 })
    478                 .collect()
    479         } else {
    480             Vec::new()
    481         };
    482         let permissions = json!({
    483             // TODO: Add support for Custom User Roles
    484             // See: https://bitwarden.com/help/article/user-types-access-control/#custom-role
    485             "accessEventLogs": false,
    486             "accessImportExport": false,
    487             "accessReports": false,
    488             "createNewCollections": false,
    489             "editAnyCollection": false,
    490             "deleteAnyCollection": false,
    491             "editAssignedCollections": false,
    492             "deleteAssignedCollections": false,
    493             "manageGroups": false,
    494             "managePolicies": false,
    495             "manageSso": false, // Not supported
    496             "manageUsers": false,
    497             "manageResetPassword": false,
    498             "manageScim": false // Not supported (Not AGPLv3 Licensed)
    499         });
    500         json!({
    501             "id": self.uuid,
    502             "userId": self.user_uuid,
    503             "name": if self.get_unrevoked_status() >= i32::from(UserOrgStatus::Accepted) { Some(user.name) } else { None },
    504             "email": user.email,
    505             "externalId": self.external_id,
    506             "avatarColor": user.avatar_color,
    507             "groups": groups,
    508             "collections": collections,
    509             "status": status,
    510             "type": self.atype,
    511             "accessAll": self.access_all,
    512             "twoFactorEnabled": twofactor_enabled,
    513             "resetPasswordEnrolled": self.reset_password_key.is_some(),
    514             "hasMasterPassword": !user.password_hash.is_empty(),
    515             "permissions": permissions,
    516             "ssoBound": false, // Not supported
    517             "usesKeyConnector": false, // Not supported
    518             "accessSecretsManager": false, // Not supported (Not AGPLv3 Licensed)
    519             "object": "organizationUserUserDetails",
    520         })
    521     }
    522 
    523     pub fn to_json_user_access_restrictions(&self, col_user: &CollectionUser) -> Value {
    524         json!({
    525             "id": self.uuid,
    526             "readOnly": col_user.read_only,
    527             "hidePasswords": col_user.hide_passwords,
    528         })
    529     }
    530 
    531     pub async fn save(&self, conn: &DbConn) -> EmptyResult {
    532         User::update_uuid_revision(&self.user_uuid, conn).await;
    533         db_run! { conn:
    534              {
    535                 match diesel::replace_into(users_organizations::table)
    536                     .values(UserOrganizationDb::to_db(self))
    537                     .execute(conn)
    538                 {
    539                     Ok(_) => Ok(()),
    540                     // Record already exists and causes a Foreign Key Violation because replace_into() wants to delete the record first.
    541                     Err(result::Error::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, _)) => {
    542                         diesel::update(users_organizations::table)
    543                             .filter(users_organizations::uuid.eq(&self.uuid))
    544                             .set(UserOrganizationDb::to_db(self))
    545                             .execute(conn)
    546                             .map_res("Error adding user to organization")
    547                     },
    548                     Err(e) => Err(e.into()),
    549                 }.map_res("Error adding user to organization")
    550             }
    551         }
    552     }
    553 
    554     pub async fn delete(self, conn: &DbConn) -> EmptyResult {
    555         User::update_uuid_revision(&self.user_uuid, conn).await;
    556         CollectionUser::delete_all_by_user_and_org(&self.user_uuid, &self.org_uuid, conn).await?;
    557         db_run! { conn: {
    558             diesel::delete(users_organizations::table.filter(users_organizations::uuid.eq(self.uuid)))
    559                 .execute(conn)
    560                 .map_res("Error removing user from organization")
    561         }}
    562     }
    563 
    564     async fn delete_all_by_organization(org_uuid: &str, conn: &DbConn) -> EmptyResult {
    565         for user_org in Self::find_by_org(org_uuid, conn).await {
    566             user_org.delete(conn).await?;
    567         }
    568         Ok(())
    569     }
    570 
    571     pub async fn delete_all_by_user(user_uuid: &str, conn: &DbConn) -> EmptyResult {
    572         for user_org in Self::find_any_state_by_user(user_uuid, conn).await {
    573             user_org.delete(conn).await?;
    574         }
    575         Ok(())
    576     }
    577 
    578     pub async fn find_by_email_and_org(email: &str, org_id: &str, conn: &DbConn) -> Option<Self> {
    579         if let Some(user) = super::User::find_by_mail(email, conn).await {
    580             if let Some(user_org) = Self::find_by_user_and_org(&user.uuid, org_id, conn).await {
    581                 return Some(user_org);
    582             }
    583         }
    584 
    585         None
    586     }
    587 
    588     pub fn has_status(&self, status: UserOrgStatus) -> bool {
    589         self.status == i32::from(status)
    590     }
    591 
    592     pub fn has_full_access(&self) -> bool {
    593         (self.access_all || self.atype >= UserOrgType::Admin)
    594             && self.has_status(UserOrgStatus::Confirmed)
    595     }
    596 
    597     pub async fn find_by_uuid(uuid: &str, conn: &DbConn) -> Option<Self> {
    598         db_run! { conn: {
    599             users_organizations::table
    600                 .filter(users_organizations::uuid.eq(uuid))
    601                 .first::<UserOrganizationDb>(conn)
    602                 .ok().from_db()
    603         }}
    604     }
    605 
    606     pub async fn find_by_uuid_and_org(uuid: &str, org_uuid: &str, conn: &DbConn) -> Option<Self> {
    607         db_run! { conn: {
    608             users_organizations::table
    609                 .filter(users_organizations::uuid.eq(uuid))
    610                 .filter(users_organizations::org_uuid.eq(org_uuid))
    611                 .first::<UserOrganizationDb>(conn)
    612                 .ok().from_db()
    613         }}
    614     }
    615 
    616     pub async fn find_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
    617         db_run! { conn: {
    618             users_organizations::table
    619                 .filter(users_organizations::user_uuid.eq(user_uuid))
    620                 .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
    621                 .load::<UserOrganizationDb>(conn)
    622                 .unwrap_or_default().from_db()
    623         }}
    624     }
    625 
    626     pub async fn find_invited_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
    627         db_run! { conn: {
    628             users_organizations::table
    629                 .filter(users_organizations::user_uuid.eq(user_uuid))
    630                 .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Invited)))
    631                 .load::<UserOrganizationDb>(conn)
    632                 .unwrap_or_default().from_db()
    633         }}
    634     }
    635 
    636     async fn find_any_state_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
    637         db_run! { conn: {
    638             users_organizations::table
    639                 .filter(users_organizations::user_uuid.eq(user_uuid))
    640                 .load::<UserOrganizationDb>(conn)
    641                 .unwrap_or_default().from_db()
    642         }}
    643     }
    644 
    645     pub async fn count_accepted_and_confirmed_by_user(user_uuid: &str, conn: &DbConn) -> u64 {
    646         u64::try_from({
    647             db_run! { conn: {
    648                 users_organizations::table
    649                     .filter(users_organizations::user_uuid.eq(user_uuid))
    650                     .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Accepted))
    651                     .or(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))))
    652                     .count()
    653                     .first::<i64>(conn)
    654                     .unwrap_or(0)
    655             }}
    656         })
    657         .expect("underflow")
    658     }
    659 
    660     pub async fn find_by_org(org_uuid: &str, conn: &DbConn) -> Vec<Self> {
    661         db_run! { conn: {
    662             users_organizations::table
    663                 .filter(users_organizations::org_uuid.eq(org_uuid))
    664                 .load::<UserOrganizationDb>(conn)
    665                 .expect("Error loading user organizations").from_db()
    666         }}
    667     }
    668 
    669     pub async fn find_by_org_and_type(
    670         org_uuid: &str,
    671         atype: UserOrgType,
    672         conn: &DbConn,
    673     ) -> Vec<Self> {
    674         db_run! { conn: {
    675             users_organizations::table
    676                 .filter(users_organizations::org_uuid.eq(org_uuid))
    677                 .filter(users_organizations::atype.eq(i32::from(atype)))
    678                 .load::<UserOrganizationDb>(conn)
    679                 .expect("Error loading user organizations").from_db()
    680         }}
    681     }
    682 
    683     pub async fn count_confirmed_by_org_and_type(
    684         org_uuid: &str,
    685         atype: UserOrgType,
    686         conn: &DbConn,
    687     ) -> u64 {
    688         u64::try_from({
    689             db_run! { conn: {
    690                 users_organizations::table
    691                     .filter(users_organizations::org_uuid.eq(org_uuid))
    692                     .filter(users_organizations::atype.eq(i32::from(atype)))
    693                     .filter(users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed)))
    694                     .count()
    695                     .first::<i64>(conn)
    696                     .unwrap_or(0)
    697             }}
    698         })
    699         .expect("underflow")
    700     }
    701 
    702     pub async fn find_by_user_and_org(
    703         user_uuid: &str,
    704         org_uuid: &str,
    705         conn: &DbConn,
    706     ) -> Option<Self> {
    707         db_run! { conn: {
    708             users_organizations::table
    709                 .filter(users_organizations::user_uuid.eq(user_uuid))
    710                 .filter(users_organizations::org_uuid.eq(org_uuid))
    711                 .first::<UserOrganizationDb>(conn)
    712                 .ok().from_db()
    713         }}
    714     }
    715 
    716     pub async fn find_confirmed_by_user_and_org(
    717         user_uuid: &str,
    718         org_uuid: &str,
    719         conn: &DbConn,
    720     ) -> Option<Self> {
    721         db_run! { conn: {
    722             users_organizations::table
    723                 .filter(users_organizations::user_uuid.eq(user_uuid))
    724                 .filter(users_organizations::org_uuid.eq(org_uuid))
    725                 .filter(
    726                     users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
    727                 )
    728                 .first::<UserOrganizationDb>(conn)
    729                 .ok().from_db()
    730         }}
    731     }
    732 
    733     pub async fn find_by_user(user_uuid: &str, conn: &DbConn) -> Vec<Self> {
    734         db_run! { conn: {
    735             users_organizations::table
    736                 .filter(users_organizations::user_uuid.eq(user_uuid))
    737                 .load::<UserOrganizationDb>(conn)
    738                 .expect("Error loading user organizations").from_db()
    739         }}
    740     }
    741 
    742     pub async fn find_by_user_and_policy(
    743         user_uuid: &str,
    744         policy_type: OrgPolicyType,
    745         conn: &DbConn,
    746     ) -> Vec<Self> {
    747         db_run! { conn: {
    748             users_organizations::table
    749                 .inner_join(
    750                     org_policies::table.on(
    751                         org_policies::org_uuid.eq(users_organizations::org_uuid)
    752                             .and(users_organizations::user_uuid.eq(user_uuid))
    753                             .and(org_policies::atype.eq(i32::from(policy_type)))
    754                             .and(org_policies::enabled.eq(true)))
    755                 )
    756                 .filter(
    757                     users_organizations::status.eq(i32::from(UserOrgStatus::Confirmed))
    758                 )
    759                 .select(users_organizations::all_columns)
    760                 .load::<UserOrganizationDb>(conn)
    761                 .unwrap_or_default().from_db()
    762         }}
    763     }
    764 
    765     pub async fn find_by_cipher_and_org(
    766         cipher_uuid: &str,
    767         org_uuid: &str,
    768         conn: &DbConn,
    769     ) -> Vec<Self> {
    770         db_run! { conn: {
    771             users_organizations::table
    772             .filter(users_organizations::org_uuid.eq(org_uuid))
    773             .left_join(users_collections::table.on(
    774                 users_collections::user_uuid.eq(users_organizations::user_uuid)
    775             ))
    776             .left_join(ciphers_collections::table.on(
    777                 ciphers_collections::collection_uuid.eq(users_collections::collection_uuid).and(
    778                     ciphers_collections::cipher_uuid.eq(&cipher_uuid)
    779                 )
    780             ))
    781             .filter(
    782                 users_organizations::access_all.eq(true).or( // AccessAll..
    783                     ciphers_collections::cipher_uuid.eq(&cipher_uuid) // ..or access to collection with cipher
    784                 )
    785             )
    786             .select(users_organizations::all_columns)
    787             .distinct()
    788             .load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db()
    789         }}
    790     }
    791 
    792     pub async fn find_by_collection_and_org(
    793         collection_uuid: &str,
    794         org_uuid: &str,
    795         conn: &DbConn,
    796     ) -> Vec<Self> {
    797         db_run! { conn: {
    798             users_organizations::table
    799             .filter(users_organizations::org_uuid.eq(org_uuid))
    800             .left_join(users_collections::table.on(
    801                 users_collections::user_uuid.eq(users_organizations::user_uuid)
    802             ))
    803             .filter(
    804                 users_organizations::access_all.eq(true).or( // AccessAll..
    805                     users_collections::collection_uuid.eq(&collection_uuid) // ..or access to collection with cipher
    806                 )
    807             )
    808             .select(users_organizations::all_columns)
    809             .load::<UserOrganizationDb>(conn).expect("Error loading user organizations").from_db()
    810         }}
    811     }
    812 }